diff --git a/Avalonia.Gif/Avalonia.Gif.csproj b/Avalonia.Gif/Avalonia.Gif.csproj
index 6a363589..dbf0defd 100644
--- a/Avalonia.Gif/Avalonia.Gif.csproj
+++ b/Avalonia.Gif/Avalonia.Gif.csproj
@@ -10,7 +10,7 @@
true
-
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb439da1..e70bc12b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,106 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
+## v2.10.0
+### Added
+- Added Reference-Only mode for Inference ControlNet, used for guiding the sampler with an image without a pretrained model. Part of the latent and attention layers will be connected to the reference image, similar to Image to Image or Inpainting.
+- Inference ControlNet module now supports over 42 preprocessors, a new button next to the preprocessors dropdown allows previewing the output of the selected preprocessor on the image.
+- Added resolution selection for Inference ControlNet module, this controls preprocessor resolution too.
+- Added Layer Diffuse sampler addon to Inference, allows generating foreground with transparency with SD1.5 and SDXL.
+- Added support for deep links from the new Stability Matrix Chrome extension
+- Added OpenArt.AI workflow browser for ComfyUI workflows
+- Added more metadata to the image dialog info flyout
+- Added Output Sharing toggle in Advanced Options during install flow
+### Changed
+- Revamped the Packages page to enable running multiple packages at the same time
+- Changed the Outputs Page to use a TreeView for the directory selection instead of a dropdown selector
+- Model download location selector now searches all subfolders
+- Inference Primary Sampler Addons (i.e. ControlNet, FreeU) are now inherited by Hires Fix Samplers, this can be overriden from the Hires Fix module's settings menu by disabling the "Inherit Primary Sampler Addons" option.
+- Revisited the way images are loaded on the outputs page, with improvements to loading speed & not freezing the UI while loading
+- Updated translations for French, Spanish, and Turkish
+- Changed to a new image control for pages with many images
+- (Internal) Updated to Avalonia 11.0.10
+### Fixed
+- Fixed [#559](https://github.com/LykosAI/StabilityMatrix/issues/559) - "Unable to load bitmap from provided data" error in Checkpoints page
+- Fixed [#522](https://github.com/LykosAI/StabilityMatrix/issues/522) - Incorrect output directory path for latest Auto1111
+- Fixed [#529](https://github.com/LykosAI/StabilityMatrix/issues/529) - OneTrainer requesting input during update
+- Fixed Civitai model browser error when sorting by Installed with more than 100 installed models
+- Fixed CLIP Install errors due to setuptools distutils conflict, added default environment variable setting `SETUPTOOLS_USE_DISTUTILS=stdlib`
+- Fixed progress bars not displaying properly during package installs & updates
+- Fixed ComfyUI extension updates not running install.py / updating requirements.txt
+- Improved performance when deleting many images from the Outputs page
+- Fixed ComfyUI torch downgrading to 2.1.2 when updating
+- Fixed Inference HiresFix module "Inherit Primary Sampler Addons" setting not effectively disabling when unchecked
+- Fixed model download location options for VAEs in the CivitAI Model Browser
+### Removed
+- Removed the main Launch page, as it is no longer needed with the new Packages page
+
+## v2.10.0-pre.2
+### Added
+- Added more metadata to the image dialog info flyout
+- Added Restart button to console page
+### Changed
+- Model download location selector now searches all subfolders
+### Fixed
+- Fixed Civitai model browser not showing images when "Show NSFW" is disabled
+- Fixed crash when Installed Workflows page is opened with no Workflows folder
+- Fixed progress bars not displaying properly during package installs & updates
+- Fixed ComfyUI extension updates not running install.py / updating requirements.txt
+
+## v2.10.0-pre.1
+### Added
+- Added OpenArt.AI workflow browser for ComfyUI workflows
+- Added Output Sharing toggle in Advanced Options during install flow
+### Changed
+- Changed to a new image control for pages with many images
+- Removed Symlink option for InvokeAI due to changes with InvokeAI v4.0+
+- Output sharing is now enabled by default for new installations
+- (Internal) Updated to Avalonia 11.0.10
+### Fixed
+- Improved performance when deleting many images from the Outputs page
+- Fixed ComfyUI torch downgrading to 2.1.2 when updating
+- Fixed [#529](https://github.com/LykosAI/StabilityMatrix/issues/529) - OneTrainer requesting input during update
+- Fixed "Could not find entry point for InvokeAI" error on InvokeAI v4.0+
+
+## v2.10.0-dev.3
+### Added
+- Added support for deep links from the new Stability Matrix Chrome extension
+### Changed
+- Due to changes on the CivitAI API, you can no longer select a specific page in the CivitAI Model Browser
+- Due to the above API changes, new pages are now loaded via "infinite scrolling"
+### Fixed
+- Fixed Inference HiresFix module "Inherit Primary Sampler Addons" setting not effectively disabling when unchecked
+- Fixed model download location options for VAEs in the CivitAI Model Browser
+- Fixed crash on startup when library directory is not set
+- Fixed One-Click install progress dialog not disappearing after completion
+- Fixed ComfyUI with Inference pop-up during one-click install appearing below the visible scroll area
+- Fixed no packages being available for one-click install on PCs without a GPU
+- Fixed models not being removed from the installed models cache when deleting them from the Checkpoints page
+- Fixed missing ratings on some models in the CivitAI Model Browser
+- Fixed missing favorite count in the CivitAI Model Browser
+- Fixed recommended models not showing all SDXL models
+
+## v2.10.0-dev.2
+### Added
+- Added Reference-Only mode for Inference ControlNet, used for guiding the sampler with an image without a pretrained model. Part of the latent and attention layers will be connected to the reference image, similar to Image to Image or Inpainting.
+### Changed
+- Inference Primary Sampler Addons (i.e. ControlNet, FreeU) are now inherited by Hires Fix Samplers, this can be overriden from the Hires Fix module's settings menu by disabling the "Inherit Primary Sampler Addons" option.
+- Revisited the way images are loaded on the outputs page, with improvements to loading speed & not freezing the UI while loading
+### Fixed
+- Fixed Outputs page not remembering where the user last was in the TreeView in certain circumstances
+- Fixed Inference extension upgrades not being added to missing extensions list for prompted install
+- Fixed "The Open Web UI button has moved" teaching tip spam
+
+## v2.10.0-dev.1
+### Added
+- Inference ControlNet module now supports over 42 preprocessors, a new button next to the preprocessors dropdown allows previewing the output of the selected preprocessor on the image.
+- Added resolution selection for Inference ControlNet module, this controls preprocessor resolution too.
+### Changed
+- Revamped the Packages page to enable running multiple packages at the same time
+- Changed the Outputs Page to use a TreeView for the directory selection instead of a dropdown selector
+### Removed
+- Removed the main Launch page, as it is no longer needed with the new Packages page
+
## v2.9.3
### Changed
- Removed Symlink option for InvokeAI to prevent InvokeAI from moving models into its own directories (will be replaced with a Config option in a future update)
@@ -22,9 +122,6 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Fixed One-Click install progress dialog not disappearing after completion
- Fixed ComfyUI with Inference pop-up during one-click install appearing below the visible scroll area
- Fixed no packages being available for one-click install on PCs without a GPU
-- Fixed missing ratings on some models in the CivitAI Model Browser
-- Fixed missing favorite count in the CivitAI Model Browser
-- Fixed recommended models not showing all SDXL models
## v2.9.1
### Added
@@ -82,6 +179,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Added copy image support on linux and macOS for Inference outputs viewer menu
### Fixed
- Fixed StableSwarmUI not installing properly on macOS
+- Fixed output sharing for Stable Diffusion WebUI Forge
- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS
- Fixed default command line args for SDWebUI Forge on macOS
- Fixed output paths and output sharing for SDWebUI Forge
diff --git a/README.md b/README.md
index a39488a7..3fd70ec3 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
[![Build](https://github.com/LykosAI/StabilityMatrix/actions/workflows/build.yml/badge.svg)](https://github.com/LykosAI/StabilityMatrix/actions/workflows/build.yml)
[![Discord Server](https://img.shields.io/discord/1115555685476868168?logo=discord&logoColor=white&label=Discord%20Server)](https://discord.com/invite/TUrgfECxHz)
+[![Release](https://img.shields.io/github/v/release/LykosAI/StabilityMatrix?label=Latest%20Release&link=https%3A%2F%2Fgithub.com%2FLykosAI%2FStabilityMatrix%2Freleases%2Flatest)][release]
[release]: https://github.com/LykosAI/StabilityMatrix/releases/latest
[download-win-x64]: https://github.com/LykosAI/StabilityMatrix/releases/latest/download/StabilityMatrix-win-x64.zip
@@ -9,7 +10,7 @@
[download-macos-arm64]: https://github.com/LykosAI/StabilityMatrix/releases/latest/download/StabilityMatrix-macos-arm64.dmg
[auto1111]: https://github.com/AUTOMATIC1111/stable-diffusion-webui
-[auto1111-directml]: https://github.com/lshqqytiger/stable-diffusion-webui-directml
+[sdwebui-directml]: https://github.com/lshqqytiger/stable-diffusion-webui-directml
[webui-ux]: https://github.com/anapnoe/stable-diffusion-webui-ux
[comfy]: https://github.com/comfyanonymous/ComfyUI
[sdnext]: https://github.com/vladmandic/automatic
@@ -27,9 +28,13 @@
[civitai]: https://civitai.com/
[huggingface]: https://huggingface.co/
-Multi-Platform Package Manager and Inference UI for Stable Diffusion
+![Header image for Stability Matrix, Multi-Platform Package Manager and Inference UI for Stable Diffusion](https://cdn.lykos.ai/static/sm-banner-rounded.webp)
-### ✨ New in 2.5 - [Inference](#inference-A-reimagined-built-in-Stable-Diffusion-experience), a built-in interface for Stable Diffusion powered by ComfyUI
+[![Windows](https://img.shields.io/badge/Windows%2010,%2011-%230079d5.svg?style=for-the-badge&logo=Windows%2011&logoColor=white)][download-win-x64]
+[![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)][download-linux-x64]
+[![macOS](https://img.shields.io/badge/mac%20os%20%28apple%20silicon%29-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)][download-macos-arm64]
+
+Multi-Platform Package Manager and Inference UI for Stable Diffusion
### 🖱️ One click install and update for Stable Diffusion Web UI Packages
- Supports:
@@ -41,31 +46,12 @@ Multi-Platform Package Manager and Inference UI for Stable Diffusion
- [InvokeAI][invokeai]
- [Kohya's GUI][kohya-ss]
- [OneTrainer][onetrainer]
-- Manage plugins / extensions for supported packages ([Automatic 1111][auto1111], [Comfy UI][comfy])
+- Manage plugins / extensions for supported packages ([Automatic1111][auto1111], [Comfy UI][comfy], [SD Web UI-UX][webui-ux], and [SD.Next][sdnext])
- Easily install or update Python dependencies for each package
- Embedded Git and Python dependencies, with no need for either to be globally installed
- Fully portable - move Stability Matrix's Data Directory to a new drive or computer at any time
-### 🚀 Launcher with syntax highlighted terminal emulator, routed GUI input prompts
-- Launch arguments editor with predefined or custom options for each Package install
-- Configurable Environment Variables
-
-### 🗃️ Checkpoint Manager, configured to be shared by all Package installs
-- Option to find CivitAI metadata and preview thumbnails for new local imports
-
-### ☁️ Model Browser to import from [CivitAI][civitai] and [HuggingFace][huggingface]
-- Automatically imports to the associated model folder depending on the model type
-- Downloads relevant metadata files and preview image
-
-![header](https://cdn.lykos.ai/static/sm-banner-rounded.webp)
-
-[![Release](https://img.shields.io/github/v/release/LykosAI/StabilityMatrix?label=Latest%20Release&link=https%3A%2F%2Fgithub.com%2FLykosAI%2FStabilityMatrix%2Freleases%2Flatest)][release]
-
-[![Windows](https://img.shields.io/badge/Windows%2010,%2011-%230079d5.svg?style=for-the-badge&logo=Windows%2011&logoColor=white)][download-win-x64]
-[![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)][download-linux-x64]
-[![macOS](https://img.shields.io/badge/mac%20os%20%28apple%20silicon%29-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)][download-macos-arm64]
-
-### Inference - A reimagined built-in Stable Diffusion experience
+### ✨ Inference - A Reimagined Interface for Stable Diffusion, Built-In to Stability Matrix
- Powerful auto-completion and syntax highlighting using a formal language grammar
- Workspaces open in tabs that save and load from `.smproj` project files
@@ -79,13 +65,20 @@ Multi-Platform Package Manager and Inference UI for Stable Diffusion
-### Searchable launch options
+### 🚀 Launcher with syntax highlighted terminal emulator, routed GUI input prompts
+- Launch arguments editor with predefined or custom options for each Package install
+- Configurable Environment Variables
+
-### Model browser powered by [Civit AI][civitai]
-- Downloads new models, automatically uses the appropriate shared model directory
+### 🗃️ Checkpoint Manager, configured to be shared by all Package installs
+- Option to find CivitAI metadata and preview thumbnails for new local imports
+
+### ☁️ Model Browser to import from [CivitAI][civitai] and [HuggingFace][huggingface]
+- Automatically imports to the associated model folder depending on the model type
+- Downloads relevant metadata files and preview image
- Pause and resume downloads, even after closing the app
diff --git a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
index 71efd8bb..2fbf2fd4 100644
--- a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
+++ b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
@@ -19,12 +19,12 @@
-
-
+
+
-
+
diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml
index cbe8b808..d6db2ffd 100644
--- a/StabilityMatrix.Avalonia/App.axaml
+++ b/StabilityMatrix.Avalonia/App.axaml
@@ -4,7 +4,7 @@
xmlns:local="using:StabilityMatrix.Avalonia"
xmlns:idcr="using:Dock.Avalonia.Controls.Recycling"
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
- xmlns:controls="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
+ xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
Name="Stability Matrix"
RequestedThemeVariant="Dark">
@@ -29,6 +29,7 @@
+
@@ -55,6 +56,7 @@
+
@@ -80,7 +82,8 @@
-
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs
new file mode 100644
index 00000000..a8973a3f
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs
@@ -0,0 +1,7 @@
+using Avalonia.Controls.Primitives;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+[Transient]
+public class LayerDiffuseCard : TemplatedControl;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml
index 7c53337d..12423e48 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml
@@ -8,6 +8,8 @@
xmlns:icons="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
x:DataType="vmInference:PromptCardViewModel">
@@ -56,21 +58,29 @@
HorizontalAlignment="Right"
Orientation="Horizontal">
-
-
+
+
+
+
+
+
-
+
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
diff --git a/StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml b/StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml
index c94276b1..40289621 100644
--- a/StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml
+++ b/StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml
@@ -1,7 +1,9 @@
+ xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
+ xmlns:controls1="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
+ xmlns:vendorLabs="clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs">
@@ -38,7 +40,7 @@
CornerRadius="12"
Command="{TemplateBinding Command}"
CommandParameter="{TemplateBinding CommandParameter}">
- IsSelectedProperty =
ToggleButton.IsCheckedProperty.AddOwner();
- public static readonly StyledProperty SourceProperty =
- AdvancedImage.SourceProperty.AddOwner();
+ public static readonly StyledProperty SourceProperty = AvaloniaProperty.Register<
+ SelectableImageButton,
+ Uri?
+ >("Source");
public static readonly StyledProperty ImageWidthProperty = AvaloniaProperty.Register<
SelectableImageButton,
@@ -48,7 +50,7 @@ public class SelectableImageButton : Button
set => SetValue(IsSelectedProperty, value);
}
- public string? Source
+ public Uri? Source
{
get => GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/AsyncImageFailedEventArgs.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/AsyncImageFailedEventArgs.cs
new file mode 100644
index 00000000..b236c348
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/AsyncImageFailedEventArgs.cs
@@ -0,0 +1,20 @@
+using System;
+using Avalonia.Interactivity;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs;
+
+public partial class BetterAsyncImage
+{
+ public class AsyncImageFailedEventArgs : RoutedEventArgs
+ {
+ internal AsyncImageFailedEventArgs(Exception? errorException = null, string errorMessage = "")
+ : base(FailedEvent)
+ {
+ ErrorException = errorException;
+ ErrorMessage = errorMessage;
+ }
+
+ public Exception? ErrorException { get; private set; }
+ public string ErrorMessage { get; private set; }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Events.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Events.cs
new file mode 100644
index 00000000..3219a46d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Events.cs
@@ -0,0 +1,42 @@
+using System;
+using Avalonia.Interactivity;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs;
+
+public partial class BetterAsyncImage
+{
+ ///
+ /// Deines the event
+ ///
+ public static readonly RoutedEvent OpenedEvent = RoutedEvent.Register<
+ BetterAsyncImage,
+ RoutedEventArgs
+ >(nameof(Opened), RoutingStrategies.Bubble);
+
+ ///
+ /// Deines the event
+ ///
+ public static readonly RoutedEvent FailedEvent =
+ RoutedEvent.Register<
+ BetterAsyncImage,
+ global::Avalonia.Labs.Controls.AsyncImage.AsyncImageFailedEventArgs
+ >(nameof(Failed), RoutingStrategies.Bubble);
+
+ ///
+ /// Occurs when the image is successfully loaded.
+ ///
+ public event EventHandler? Opened
+ {
+ add => AddHandler(OpenedEvent, value);
+ remove => RemoveHandler(OpenedEvent, value);
+ }
+
+ ///
+ /// Occurs when the image fails to load the uri provided.
+ ///
+ public event EventHandler? Failed
+ {
+ add => AddHandler(FailedEvent, value);
+ remove => RemoveHandler(FailedEvent, value);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Properties.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Properties.cs
new file mode 100644
index 00000000..c31f00aa
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Properties.cs
@@ -0,0 +1,135 @@
+using System;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Labs.Controls;
+using Avalonia.Media;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs;
+
+public partial class BetterAsyncImage
+{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlaceholderSourceProperty = AvaloniaProperty.Register<
+ BetterAsyncImage,
+ IImage?
+ >(nameof(PlaceholderSource));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty SourceProperty = AvaloniaProperty.Register<
+ BetterAsyncImage,
+ Uri?
+ >(nameof(Source));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StretchProperty = AvaloniaProperty.Register<
+ BetterAsyncImage,
+ Stretch
+ >(nameof(Stretch), Stretch.Uniform);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlaceholderStretchProperty = AvaloniaProperty.Register<
+ BetterAsyncImage,
+ Stretch
+ >(nameof(PlaceholderStretch), Stretch.Uniform);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty StateProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(State),
+ o => o.State,
+ (o, v) => o.State = v
+ );
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ImageTransitionProperty =
+ AvaloniaProperty.Register(
+ nameof(ImageTransition),
+ new CrossFade(TimeSpan.FromSeconds(0.25))
+ );
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty IsCacheEnabledProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(IsCacheEnabled),
+ o => o.IsCacheEnabled,
+ (o, v) => o.IsCacheEnabled = v
+ );
+ private bool _isCacheEnabled;
+
+ ///
+ /// Gets or sets the placeholder image.
+ ///
+ public IImage? PlaceholderSource
+ {
+ get => GetValue(PlaceholderSourceProperty);
+ set => SetValue(PlaceholderSourceProperty, value);
+ }
+
+ ///
+ /// Gets or sets the uri pointing to the image resource
+ ///
+ public Uri? Source
+ {
+ get => GetValue(SourceProperty);
+ set => SetValue(SourceProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value controlling how the image will be stretched.
+ ///
+ public Stretch Stretch
+ {
+ get { return GetValue(StretchProperty); }
+ set { SetValue(StretchProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value controlling how the placeholder will be stretched.
+ ///
+ public Stretch PlaceholderStretch
+ {
+ get { return GetValue(StretchProperty); }
+ set { SetValue(StretchProperty, value); }
+ }
+
+ ///
+ /// Gets the current loading state of the image.
+ ///
+ public AsyncImageState State
+ {
+ get => _state;
+ private set => SetAndRaise(StateProperty, ref _state, value);
+ }
+
+ ///
+ /// Gets or sets the transition to run when the image is loaded.
+ ///
+ public IPageTransition? ImageTransition
+ {
+ get => GetValue(ImageTransitionProperty);
+ set => SetValue(ImageTransitionProperty, value);
+ }
+
+ ///
+ /// Gets or sets whether to use cache for retrieved images
+ ///
+ public bool IsCacheEnabled
+ {
+ get => _isCacheEnabled;
+ set => SetAndRaise(IsCacheEnabledProperty, ref _isCacheEnabled, value);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.cs
new file mode 100644
index 00000000..ec0cfa54
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.cs
@@ -0,0 +1,248 @@
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Labs.Controls;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using Avalonia.Threading;
+using StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs;
+
+///
+/// An image control that asynchronously retrieves an image using a .
+///
+[TemplatePart("PART_Image", typeof(Image))]
+[TemplatePart("PART_PlaceholderImage", typeof(Image))]
+public partial class BetterAsyncImage : TemplatedControl
+{
+ protected Image? ImagePart { get; private set; }
+ protected Image? PlaceholderPart { get; private set; }
+
+ private bool _isInitialized;
+ private CancellationTokenSource? _tokenSource;
+ private AsyncImageState _state;
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ ImagePart = e.NameScope.Get("PART_Image");
+ PlaceholderPart = e.NameScope.Get("PART_PlaceholderImage");
+
+ _tokenSource = new CancellationTokenSource();
+
+ _isInitialized = true;
+
+ if (Source != null)
+ {
+ SetSource(Source);
+ }
+ }
+
+ private async void SetSource(object? source)
+ {
+ if (!_isInitialized)
+ {
+ return;
+ }
+
+ _tokenSource?.Cancel();
+
+ _tokenSource = new CancellationTokenSource();
+
+ AttachSource(null);
+
+ if (source == null)
+ {
+ return;
+ }
+
+ State = AsyncImageState.Loading;
+
+ if (Source is IImage image)
+ {
+ AttachSource(image);
+
+ return;
+ }
+
+ if (Source == null)
+ {
+ return;
+ }
+
+ var uri = Source;
+
+ if (uri != null && uri.IsAbsoluteUri)
+ {
+ if (uri.Scheme == "http" || uri.Scheme == "https")
+ {
+ Bitmap? bitmap = null;
+ // Android doesn't allow network requests on the main thread, even though we are using async apis.
+#if NET6_0_OR_GREATER
+ if (OperatingSystem.IsAndroid())
+ {
+ await Task.Run(async () =>
+ {
+ try
+ {
+ bitmap = await LoadImageAsync(uri, _tokenSource.Token);
+ }
+ catch (Exception ex)
+ {
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ State = AsyncImageState.Failed;
+
+ RaiseEvent(new AsyncImageFailedEventArgs(ex));
+ });
+ }
+ });
+ }
+ else
+#endif
+ {
+ try
+ {
+ bitmap = await LoadImageAsync(uri, _tokenSource.Token);
+ }
+ catch (Exception ex)
+ {
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ State = AsyncImageState.Failed;
+
+ RaiseEvent(new AsyncImageFailedEventArgs(ex));
+ });
+ }
+ }
+
+ AttachSource(bitmap);
+ }
+ else if (uri.Scheme == "avares")
+ {
+ try
+ {
+ AttachSource(new Bitmap(AssetLoader.Open(uri)));
+ }
+ catch (Exception ex)
+ {
+ State = AsyncImageState.Failed;
+
+ RaiseEvent(new AsyncImageFailedEventArgs(ex));
+ }
+ }
+ else if (uri.Scheme == "file" && File.Exists(uri.LocalPath))
+ {
+ // Added error handling here for local files
+ try
+ {
+ AttachSource(new Bitmap(uri.LocalPath));
+ }
+ catch (Exception ex)
+ {
+ State = AsyncImageState.Failed;
+
+ RaiseEvent(new AsyncImageFailedEventArgs(ex));
+ }
+ }
+ else
+ {
+ RaiseEvent(
+ new AsyncImageFailedEventArgs(
+ new UriFormatException($"Uri has unsupported scheme. Uri:{source}")
+ )
+ );
+ }
+ }
+ else
+ {
+ RaiseEvent(
+ new AsyncImageFailedEventArgs(
+ new UriFormatException($"Relative paths aren't supported. Uri:{source}")
+ )
+ );
+ }
+ }
+
+ private void AttachSource(IImage? image)
+ {
+ if (ImagePart != null)
+ {
+ ImagePart.Source = image;
+ }
+
+ _tokenSource?.Cancel();
+ _tokenSource = new CancellationTokenSource();
+
+ if (image == null)
+ {
+ State = AsyncImageState.Unloaded;
+
+ ImageTransition?.Start(ImagePart, PlaceholderPart, true, _tokenSource.Token);
+ }
+ else if (image.Size != default)
+ {
+ State = AsyncImageState.Loaded;
+
+ ImageTransition?.Start(PlaceholderPart, ImagePart, true, _tokenSource.Token);
+
+ RaiseEvent(new RoutedEventArgs(OpenedEvent));
+ }
+ }
+
+ private async Task LoadImageAsync(Uri? url, CancellationToken token)
+ {
+ if (await ProvideCachedResourceAsync(url, token) is { } bitmap)
+ {
+ return bitmap;
+ }
+#if NET6_0_OR_GREATER
+ using var client = new HttpClient();
+ var stream = await client.GetStreamAsync(url, token).ConfigureAwait(false);
+
+ await using var memoryStream = new MemoryStream();
+ await stream.CopyToAsync(memoryStream, token).ConfigureAwait(false);
+#elif NETSTANDARD2_0
+ using var client = new HttpClient();
+ var response = await client.GetAsync(url, token).ConfigureAwait(false);
+ var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+
+ using var memoryStream = new MemoryStream();
+ await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+#endif
+
+ memoryStream.Position = 0;
+ return new Bitmap(memoryStream);
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == SourceProperty)
+ {
+ SetSource(Source);
+ }
+ }
+
+ protected virtual async Task ProvideCachedResourceAsync(Uri? imageUri, CancellationToken token)
+ {
+ if (IsCacheEnabled && imageUri != null)
+ {
+ return await ImageCache
+ .Instance.GetFromCacheAsync(imageUri, cancellationToken: token)
+ .ConfigureAwait(false);
+ }
+ return null;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheBase.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheBase.cs
new file mode 100644
index 00000000..a4604082
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheBase.cs
@@ -0,0 +1,565 @@
+// Parts of this file was taken from Windows Community Toolkit CacheBase implementation
+// https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Cache/ImageCache.cs
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
+
+internal abstract class CacheBase
+{
+ private class ConcurrentRequest
+ {
+ public Task? Task { get; set; }
+
+ public bool EnsureCachedCopy { get; set; }
+ }
+
+ private readonly SemaphoreSlim _cacheFolderSemaphore = new SemaphoreSlim(1);
+ private string? _baseFolder = null;
+ private string? _cacheFolderName = null;
+
+ private string? _cacheFolder = null;
+ private InMemoryStorage? _inMemoryFileStorage = null;
+
+ private ConcurrentDictionary _concurrentTasks =
+ new ConcurrentDictionary();
+
+ private HttpClient? _httpClient = null;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected CacheBase()
+ {
+ var options = CacheOptions.Default;
+ CacheDuration = options?.CacheDuration ?? TimeSpan.FromDays(1);
+ _baseFolder = options?.BaseCachePath ?? null;
+ _inMemoryFileStorage = new InMemoryStorage();
+ RetryCount = 1;
+ }
+
+ ///
+ /// Gets or sets the life duration of every cache entry.
+ ///
+ public TimeSpan CacheDuration { get; set; }
+
+ ///
+ /// Gets or sets the number of retries trying to ensure the file is cached.
+ ///
+ public uint RetryCount { get; set; }
+
+ ///
+ /// Gets or sets max in-memory item storage count
+ ///
+ public int MaxMemoryCacheCount
+ {
+ get { return _inMemoryFileStorage?.MaxItemCount ?? 0; }
+ set
+ {
+ if (_inMemoryFileStorage != null)
+ _inMemoryFileStorage.MaxItemCount = value;
+ }
+ }
+
+ ///
+ /// Gets instance of
+ ///
+ protected HttpClient HttpClient
+ {
+ get
+ {
+ if (_httpClient == null)
+ {
+ var messageHandler = new HttpClientHandler();
+
+ _httpClient = new HttpClient(messageHandler);
+ }
+
+ return _httpClient;
+ }
+ }
+
+ ///
+ /// Initializes FileCache and provides root folder and cache folder name
+ ///
+ /// Folder that is used as root for cache
+ /// Cache folder name
+ /// instance of
+ /// awaitable task
+ public virtual async Task InitializeAsync(
+ string? folder = null,
+ string? folderName = null,
+ HttpMessageHandler? httpMessageHandler = null
+ )
+ {
+ _baseFolder = folder;
+ _cacheFolderName = folderName;
+
+ _cacheFolder = await GetCacheFolderAsync().ConfigureAwait(false);
+
+ if (httpMessageHandler != null)
+ {
+ _httpClient = new HttpClient(httpMessageHandler);
+ }
+ }
+
+ ///
+ /// Clears all files in the cache
+ ///
+ /// awaitable task
+ public async Task ClearAsync()
+ {
+ var folder = await GetCacheFolderAsync().ConfigureAwait(false);
+ var files = Directory.EnumerateFiles(folder!);
+
+ await InternalClearAsync(files.Select(x => x as string)).ConfigureAwait(false);
+
+ _inMemoryFileStorage?.Clear();
+ }
+
+ ///
+ /// Clears file if it has expired
+ ///
+ /// timespan to compute whether file has expired or not
+ /// awaitable task
+ public Task ClearAsync(TimeSpan duration)
+ {
+ return RemoveExpiredAsync(duration);
+ }
+
+ ///
+ /// Removes cached files that have expired
+ ///
+ /// Optional timespan to compute whether file has expired or not. If no value is supplied, is used.
+ /// awaitable task
+ public async Task RemoveExpiredAsync(TimeSpan? duration = null)
+ {
+ TimeSpan expiryDuration = duration ?? CacheDuration;
+
+ var folder = await GetCacheFolderAsync().ConfigureAwait(false);
+ var files = Directory.EnumerateFiles(folder!);
+
+ var filesToDelete = new List();
+
+ foreach (var file in files)
+ {
+ if (file == null)
+ {
+ continue;
+ }
+
+ if (await IsFileOutOfDateAsync(file, expiryDuration, false).ConfigureAwait(false))
+ {
+ filesToDelete.Add(file);
+ }
+ }
+
+ await InternalClearAsync(filesToDelete).ConfigureAwait(false);
+
+ _inMemoryFileStorage?.Clear(expiryDuration);
+ }
+
+ ///
+ /// Removed items based on uri list passed
+ ///
+ /// Enumerable uri list
+ /// awaitable Task
+ public async Task RemoveAsync(IEnumerable uriForCachedItems)
+ {
+ if (uriForCachedItems == null || !uriForCachedItems.Any())
+ {
+ return;
+ }
+
+ var folder = await GetCacheFolderAsync().ConfigureAwait(false);
+ var files = Directory.EnumerateFiles(folder!);
+ var filesToDelete = new List();
+ var keys = new List();
+
+ Dictionary hashDictionary = new Dictionary();
+
+ foreach (var file in files)
+ {
+ hashDictionary.Add(Path.GetFileName(file), file);
+ }
+
+ foreach (var uri in uriForCachedItems)
+ {
+ string fileName = GetCacheFileName(uri);
+ if (hashDictionary.TryGetValue(fileName, out var file))
+ {
+ filesToDelete.Add(file);
+ keys.Add(fileName);
+ }
+ }
+
+ await InternalClearAsync(filesToDelete).ConfigureAwait(false);
+
+ _inMemoryFileStorage?.Remove(keys);
+ }
+
+ ///
+ /// Assures that item represented by Uri is cached.
+ ///
+ /// Uri of the item
+ /// Indicates whether or not exception should be thrown if item cannot be cached
+ /// Indicates if item should be loaded into the in-memory storage
+ /// instance of
+ /// Awaitable Task
+ public Task PreCacheAsync(
+ Uri uri,
+ bool throwOnError = false,
+ bool storeToMemoryCache = false,
+ CancellationToken cancellationToken = default(CancellationToken)
+ )
+ {
+ return GetItemAsync(uri, throwOnError, !storeToMemoryCache, cancellationToken);
+ }
+
+ ///
+ /// Retrieves item represented by Uri from the cache. If the item is not found in the cache, it will try to downloaded and saved before returning it to the caller.
+ ///
+ /// Uri of the item.
+ /// Indicates whether or not exception should be thrown if item cannot be found / downloaded.
+ /// instance of
+ /// an instance of Generic type
+ public Task GetFromCacheAsync(
+ Uri uri,
+ bool throwOnError = false,
+ CancellationToken cancellationToken = default(CancellationToken)
+ )
+ {
+ return GetItemAsync(uri, throwOnError, false, cancellationToken);
+ }
+
+ ///
+ /// Gets the string containing cached item for given Uri
+ ///
+ /// Uri of the item.
+ /// a string
+ public async Task GetFileFromCacheAsync(Uri uri)
+ {
+ var folder = await GetCacheFolderAsync().ConfigureAwait(false);
+
+ return Path.Combine(folder!, GetCacheFileName(uri));
+ }
+
+ ///
+ /// Retrieves item represented by Uri from the in-memory cache if it exists and is not out of date. If item is not found or is out of date, default instance of the generic type is returned.
+ ///
+ /// Uri of the item.
+ /// an instance of Generic type
+ public T? GetFromMemoryCache(Uri uri)
+ {
+ T? instance = default(T);
+
+ string fileName = GetCacheFileName(uri);
+
+ if (_inMemoryFileStorage?.MaxItemCount > 0)
+ {
+ var msi = _inMemoryFileStorage?.GetItem(fileName, CacheDuration);
+ if (msi != null)
+ {
+ instance = msi.Item;
+ }
+ }
+
+ return instance;
+ }
+
+ ///
+ /// Cache specific hooks to process items from HTTP response
+ ///
+ /// input stream
+ /// awaitable task
+ protected abstract Task ConvertFromAsync(Stream stream);
+
+ ///
+ /// Cache specific hooks to process items from HTTP response
+ ///
+ /// storage file
+ /// awaitable task
+ protected abstract Task ConvertFromAsync(string baseFile);
+
+ ///
+ /// Override-able method that checks whether file is valid or not.
+ ///
+ /// storage file
+ /// cache duration
+ /// option to mark uninitialized file as expired
+ /// bool indicate whether file has expired or not
+ protected virtual async Task IsFileOutOfDateAsync(
+ string file,
+ TimeSpan duration,
+ bool treatNullFileAsOutOfDate = true
+ )
+ {
+ if (file == null)
+ {
+ return treatNullFileAsOutOfDate;
+ }
+
+ var info = new FileInfo(file);
+
+ return info.Length == 0 || DateTime.Now.Subtract(info.LastWriteTime) > duration;
+ }
+
+ private static string GetCacheFileName(Uri uri)
+ {
+ return CreateHash64(uri.ToString()).ToString();
+ }
+
+ private static ulong CreateHash64(string str)
+ {
+ byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(str);
+
+ ulong value = (ulong)utf8.Length;
+ for (int n = 0; n < utf8.Length; n++)
+ {
+ value += (ulong)utf8[n] << ((n * 5) % 56);
+ }
+
+ return value;
+ }
+
+ private async Task GetItemAsync(
+ Uri uri,
+ bool throwOnError,
+ bool preCacheOnly,
+ CancellationToken cancellationToken
+ )
+ {
+ T? instance = default(T);
+
+ string fileName = GetCacheFileName(uri);
+ _concurrentTasks.TryGetValue(fileName, out var request);
+
+ // if similar request exists check if it was preCacheOnly and validate that current request isn't preCacheOnly
+ if (request != null && request.EnsureCachedCopy && !preCacheOnly)
+ {
+ if (request.Task != null)
+ await request.Task.ConfigureAwait(false);
+ request = null;
+ }
+
+ if (request == null)
+ {
+ request = new ConcurrentRequest()
+ {
+ Task = GetFromCacheOrDownloadAsync(uri, fileName, preCacheOnly, cancellationToken),
+ EnsureCachedCopy = preCacheOnly
+ };
+
+ _concurrentTasks[fileName] = request;
+ }
+
+ try
+ {
+ if (request.Task != null)
+ instance = await request.Task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine(ex.Message);
+ if (throwOnError)
+ {
+ throw;
+ }
+ }
+ finally
+ {
+ _concurrentTasks.TryRemove(fileName, out _);
+ }
+
+ return instance;
+ }
+
+ private async Task GetFromCacheOrDownloadAsync(
+ Uri uri,
+ string fileName,
+ bool preCacheOnly,
+ CancellationToken cancellationToken
+ )
+ {
+ T? instance = default(T);
+
+ if (_inMemoryFileStorage?.MaxItemCount > 0)
+ {
+ var msi = _inMemoryFileStorage?.GetItem(fileName, CacheDuration);
+ if (msi != null)
+ {
+ instance = msi.Item;
+ }
+ }
+
+ if (instance != null)
+ {
+ return instance;
+ }
+
+ var folder = await GetCacheFolderAsync().ConfigureAwait(false);
+ var baseFile = Path.Combine(folder!, fileName);
+
+ bool downloadDataFile =
+ !File.Exists(baseFile)
+ || await IsFileOutOfDateAsync(baseFile, CacheDuration).ConfigureAwait(false);
+
+ if (!File.Exists(baseFile))
+ {
+ File.Create(baseFile).Dispose();
+ }
+
+ if (downloadDataFile)
+ {
+ uint retries = 0;
+ try
+ {
+ while (retries < RetryCount)
+ {
+ try
+ {
+ instance = await DownloadFileAsync(uri, baseFile, preCacheOnly, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (instance != null)
+ {
+ break;
+ }
+ }
+ catch (FileNotFoundException) { }
+
+ retries++;
+ }
+ }
+ catch (Exception ex)
+ {
+ File.Delete(baseFile);
+ throw; // re-throwing the exception changes the stack trace. just throw
+ }
+ }
+
+ if (EqualityComparer.Default.Equals(instance, default(T)) && !preCacheOnly)
+ {
+ instance = await ConvertFromAsync(baseFile).ConfigureAwait(false);
+
+ if (_inMemoryFileStorage?.MaxItemCount > 0)
+ {
+ var properties = new FileInfo(baseFile);
+
+ var msi = new InMemoryStorageItem(fileName, properties.LastWriteTime, instance);
+ _inMemoryFileStorage?.SetItem(msi);
+ }
+ }
+ return instance;
+ }
+
+ private async Task DownloadFileAsync(
+ Uri uri,
+ string baseFile,
+ bool preCacheOnly,
+ CancellationToken cancellationToken
+ )
+ {
+ T? instance = default(T);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (var stream = await HttpClient.GetStreamAsync(uri))
+ {
+ stream.CopyTo(ms);
+ ms.Flush();
+
+ ms.Position = 0;
+
+ using (var fs = File.Open(baseFile, FileMode.OpenOrCreate, FileAccess.Write))
+ {
+ ms.CopyTo(fs);
+
+ fs.Flush();
+
+ ms.Position = 0;
+ }
+ }
+
+ // if its pre-cache we aren't looking to load items in memory
+ if (!preCacheOnly)
+ {
+ instance = await ConvertFromAsync(ms).ConfigureAwait(false);
+ }
+ }
+
+ return instance;
+ }
+
+ private async Task InternalClearAsync(IEnumerable files)
+ {
+ foreach (var file in files)
+ {
+ try
+ {
+ File.Delete(file!);
+ }
+ catch
+ {
+ // Just ignore errors for now}
+ }
+ }
+ }
+
+ ///
+ /// Initializes with default values if user has not initialized explicitly
+ ///
+ /// awaitable task
+ private async Task ForceInitialiseAsync()
+ {
+ if (_cacheFolder != null)
+ {
+ return;
+ }
+
+ await _cacheFolderSemaphore.WaitAsync().ConfigureAwait(false);
+
+ _inMemoryFileStorage = new InMemoryStorage();
+
+ if (_baseFolder == null)
+ {
+ _baseFolder = Path.GetTempPath();
+ }
+
+ if (string.IsNullOrWhiteSpace(_cacheFolderName))
+ {
+ _cacheFolderName = GetType().Name;
+ }
+
+ try
+ {
+ _cacheFolder = Path.Combine(_baseFolder, _cacheFolderName);
+ Directory.CreateDirectory(_cacheFolder);
+ }
+ finally
+ {
+ _cacheFolderSemaphore.Release();
+ }
+ }
+
+ private async Task GetCacheFolderAsync()
+ {
+ if (_cacheFolder == null)
+ {
+ await ForceInitialiseAsync().ConfigureAwait(false);
+ }
+
+ return _cacheFolder;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheOptions.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheOptions.cs
new file mode 100644
index 00000000..a07ca33c
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheOptions.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
+
+public class CacheOptions
+{
+ private static CacheOptions? _cacheOptions;
+
+ public static CacheOptions Default => _cacheOptions ??= new CacheOptions();
+
+ public static void SetDefault(CacheOptions defaultCacheOptions)
+ {
+ _cacheOptions = defaultCacheOptions;
+ }
+
+ public string? BaseCachePath { get; set; }
+ public TimeSpan? CacheDuration { get; set; }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/FileCache.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/FileCache.cs
new file mode 100644
index 00000000..a273ca0b
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/FileCache.cs
@@ -0,0 +1,36 @@
+using System.IO;
+using System.Threading.Tasks;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
+
+///
+/// Provides methods and tools to cache files in a folder
+///
+internal class FileCache : CacheBase
+{
+ ///
+ /// Private singleton field.
+ ///
+ private static FileCache? _instance;
+
+ ///
+ /// Gets public singleton property.
+ ///
+ public static FileCache Instance => _instance ?? (_instance = new FileCache());
+
+ protected override Task ConvertFromAsync(Stream stream)
+ {
+ // nothing to do in this instance;
+ return Task.FromResult("");
+ }
+
+ ///
+ /// Returns a cached path
+ ///
+ /// storage file
+ /// awaitable task
+ protected override Task ConvertFromAsync(string baseFile)
+ {
+ return Task.FromResult(baseFile);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/ImageCache.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/ImageCache.cs
new file mode 100644
index 00000000..5582b071
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/ImageCache.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Avalonia.Media.Imaging;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
+
+///
+/// Provides methods and tools to cache images in a folder
+///
+internal class ImageCache : CacheBase
+{
+ ///
+ /// Private singleton field.
+ ///
+ [ThreadStatic]
+ private static ImageCache? _instance;
+
+ ///
+ /// Gets public singleton property.
+ ///
+ public static ImageCache Instance => _instance ?? (_instance = new ImageCache());
+
+ ///
+ /// Creates a bitmap from a stream
+ ///
+ /// input stream
+ /// awaitable task
+ protected override async Task ConvertFromAsync(Stream stream)
+ {
+ if (stream.Length == 0)
+ {
+ throw new FileNotFoundException();
+ }
+
+ return new Bitmap(stream);
+ }
+
+ ///
+ /// Creates a bitmap from a cached file
+ ///
+ /// file
+ /// awaitable task
+ protected override async Task ConvertFromAsync(string baseFile)
+ {
+ using (var stream = File.OpenRead(baseFile))
+ {
+ return await ConvertFromAsync(stream).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Checks whether file is valid or not.
+ ///
+ /// file
+ /// cache duration
+ /// option to mark uninitialized file as expired
+ /// bool indicate whether file has expired or not
+ protected override async Task IsFileOutOfDateAsync(
+ string file,
+ TimeSpan duration,
+ bool treatNullFileAsOutOfDate = true
+ )
+ {
+ if (file == null)
+ {
+ return treatNullFileAsOutOfDate;
+ }
+
+ var fileInfo = new FileInfo(file);
+
+ return fileInfo.Length == 0
+ || DateTime.Now.Subtract(File.GetLastAccessTime(file)) > duration
+ || DateTime.Now.Subtract(File.GetLastWriteTime(file)) > duration;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorage.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorage.cs
new file mode 100644
index 00000000..5643e679
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorage.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Labs.Controls.Cache;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
+
+///
+/// Generic in-memory storage of items
+///
+/// T defines the type of item stored
+public class InMemoryStorage
+{
+ private int _maxItemCount;
+ private ConcurrentDictionary> _inMemoryStorage =
+ new ConcurrentDictionary>();
+ private object _settingMaxItemCountLocker = new object();
+
+ ///
+ /// Gets or sets the maximum count of Items that can be stored in this InMemoryStorage instance.
+ ///
+ public int MaxItemCount
+ {
+ get { return _maxItemCount; }
+ set
+ {
+ if (_maxItemCount == value)
+ {
+ return;
+ }
+
+ _maxItemCount = value;
+
+ lock (_settingMaxItemCountLocker)
+ {
+ EnsureStorageBounds(value);
+ }
+ }
+ }
+
+ ///
+ /// Clears all items stored in memory
+ ///
+ public void Clear()
+ {
+ _inMemoryStorage.Clear();
+ }
+
+ ///
+ /// Clears items stored in memory based on duration passed
+ ///
+ /// TimeSpan to identify expired items
+ public void Clear(TimeSpan duration)
+ {
+ var expirationDate = DateTime.Now.Subtract(duration);
+
+ var itemsToRemove = _inMemoryStorage
+ .Where(kvp => kvp.Value.LastUpdated <= expirationDate)
+ .Select(kvp => kvp.Key);
+
+ if (itemsToRemove.Any())
+ {
+ Remove(itemsToRemove);
+ }
+ }
+
+ ///
+ /// Remove items based on provided keys
+ ///
+ /// identified of the in-memory storage item
+ public void Remove(IEnumerable keys)
+ {
+ foreach (var key in keys)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ continue;
+ }
+
+ _inMemoryStorage.TryRemove(key, out _);
+ }
+ }
+
+ ///
+ /// Add new item to in-memory storage
+ ///
+ /// item to be stored
+ public void SetItem(InMemoryStorageItem item)
+ {
+ if (MaxItemCount == 0)
+ {
+ return;
+ }
+
+ _inMemoryStorage[item.Id] = item;
+
+ // ensure max limit is maintained. trim older entries first
+ if (_inMemoryStorage.Count > MaxItemCount)
+ {
+ var itemsToRemove = _inMemoryStorage
+ .OrderBy(kvp => kvp.Value.Created)
+ .Take(_inMemoryStorage.Count - MaxItemCount)
+ .Select(kvp => kvp.Key);
+ Remove(itemsToRemove);
+ }
+ }
+
+ ///
+ /// Get item from in-memory storage as long as it has not ex
+ ///
+ /// id of the in-memory storage item
+ /// timespan denoting expiration
+ /// Valid item if not out of date or return null if out of date or item does not exist
+ public InMemoryStorageItem? GetItem(string id, TimeSpan duration)
+ {
+ if (!_inMemoryStorage.TryGetValue(id, out var tempItem))
+ {
+ return null;
+ }
+
+ var expirationDate = DateTime.Now.Subtract(duration);
+
+ if (tempItem.LastUpdated > expirationDate)
+ {
+ return tempItem;
+ }
+
+ _inMemoryStorage.TryRemove(id, out _);
+
+ return null;
+ }
+
+ private void EnsureStorageBounds(int maxCount)
+ {
+ if (_inMemoryStorage.Count == 0)
+ {
+ return;
+ }
+
+ if (maxCount == 0)
+ {
+ _inMemoryStorage.Clear();
+ return;
+ }
+
+ if (_inMemoryStorage.Count > maxCount)
+ {
+ Remove(_inMemoryStorage.Keys.Take(_inMemoryStorage.Count - maxCount));
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorageItem.cs b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorageItem.cs
new file mode 100644
index 00000000..4bad770d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorageItem.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
+
+///
+/// Generic InMemoryStorageItem holds items for InMemoryStorage.
+///
+/// Type is set by consuming cache
+public class InMemoryStorageItem
+{
+ ///
+ /// Gets the item identifier
+ ///
+ public string Id { get; private set; }
+
+ ///
+ /// Gets the item created timestamp.
+ ///
+ public DateTime Created { get; private set; }
+
+ ///
+ /// Gets the item last updated timestamp.
+ ///
+ public DateTime LastUpdated { get; private set; }
+
+ ///
+ /// Gets the item being stored.
+ ///
+ public T Item { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Constructor for InMemoryStorageItem
+ ///
+ /// uniquely identifies the item
+ /// last updated timestamp
+ /// the item being stored
+ public InMemoryStorageItem(string id, DateTime lastUpdated, T item)
+ {
+ Id = id;
+ LastUpdated = lastUpdated;
+ Item = item;
+ Created = DateTime.Now;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/LICENSE b/StabilityMatrix.Avalonia/Controls/VendorLabs/LICENSE
new file mode 100644
index 00000000..8a6071d2
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 AvaloniaUI
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/StabilityMatrix.Avalonia/Controls/VendorLabs/Themes/BetterAsyncImage.axaml b/StabilityMatrix.Avalonia/Controls/VendorLabs/Themes/BetterAsyncImage.axaml
new file mode 100644
index 00000000..2b6f1bd0
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/VendorLabs/Themes/BetterAsyncImage.axaml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
index 263ae811..c61d19ba 100644
--- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs
+++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
@@ -36,6 +36,7 @@ using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Api.Comfy;
+using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification;
@@ -51,6 +52,7 @@ using HuggingFacePageViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointB
namespace StabilityMatrix.Avalonia.DesignData;
+[Localizable(false)]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public static class DesignData
{
@@ -98,6 +100,19 @@ public static class DesignData
},
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now
+ },
+ new()
+ {
+ Id = Guid.NewGuid(),
+ DisplayName = "Running Comfy",
+ PackageName = "ComfyUI",
+ Version = new InstalledPackageVersion
+ {
+ InstalledBranch = "master",
+ InstalledCommitSha = "abc12uwu345568972abaedf7g7e679a98879e879f87ga8"
+ },
+ LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
+ LastUpdateCheck = DateTimeOffset.Now
}
},
ActiveInstalledPackageId = activePackageId
@@ -312,7 +327,7 @@ public static class DesignData
);
}*/
- CivitAiBrowserViewModel.ModelCards = new ObservableCollection
+ CivitAiBrowserViewModel.ModelCards = new ObservableCollectionExtended
{
dialogFactory.Get(vm =>
{
@@ -405,7 +420,7 @@ public static class DesignData
new PackageModificationRunner
{
CurrentProgress = new ProgressReport(0.5f, "Installing package..."),
- ModificationCompleteMessage = "Done"
+ ModificationCompleteMessage = "Package installed successfully"
}
)
}
@@ -508,6 +523,16 @@ public static class DesignData
}
)
};
+ vm.Categories = new ObservableCollectionExtended
+ {
+ new()
+ {
+ Name = "Category 1",
+ Path = "path1",
+ SubDirectories = [new PackageOutputCategory { Name = "SubCategory 1", Path = "path3" }]
+ },
+ new() { Name = "Category 2", Path = "path2" }
+ };
return vm;
}
}
@@ -898,6 +923,39 @@ The gallery images are often inpainted, but you will get something very similar
vm.IsBatchIndexEnabled = true;
});
+ public static LayerDiffuseCardViewModel LayerDiffuseCardViewModel =>
+ DialogFactory.Get();
+
+ public static InstalledWorkflowsViewModel InstalledWorkflowsViewModel
+ {
+ get
+ {
+ var vm = Services.GetRequiredService();
+ vm.DisplayedWorkflows = new ObservableCollectionExtended
+ {
+ new()
+ {
+ Workflow = new()
+ {
+ Name = "Test Workflow",
+ Creator = new OpenArtCreator { Name = "Test Creator" },
+ Thumbnails =
+ [
+ new OpenArtThumbnail
+ {
+ Url = new Uri(
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512"
+ )
+ }
+ ]
+ }
+ }
+ };
+
+ return vm;
+ }
+ }
+
public static IList SampleCompletionData =>
new List
{
@@ -1009,6 +1067,60 @@ The gallery images are often inpainted, but you will get something very similar
public static ControlNetCardViewModel ControlNetCardViewModel =>
DialogFactory.Get();
+ public static OpenArtWorkflowViewModel OpenArtWorkflowViewModel =>
+ new(Services.GetRequiredService(), Services.GetRequiredService())
+ {
+ Workflow = new OpenArtSearchResult
+ {
+ Name = "Test Workflow",
+ Creator = new OpenArtCreator
+ {
+ Name = "Test Creator Name",
+ Username = "Test Creator Username"
+ },
+ Thumbnails =
+ [
+ new OpenArtThumbnail
+ {
+ Url = new Uri(
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500"
+ )
+ }
+ ],
+ NodesIndex =
+ [
+ "Anything Everywhere",
+ "Reroute",
+ "Note",
+ ".",
+ "ComfyUI's ControlNet Auxiliary Preprocessors",
+ "DWPreprocessor",
+ "PixelPerfectResolution",
+ "AIO_Preprocessor",
+ ",",
+ "ComfyUI",
+ "PreviewImage",
+ "CLIPTextEncode",
+ "EmptyLatentImage",
+ "SplitImageWithAlpha",
+ "ControlNetApplyAdvanced",
+ "JoinImageWithAlpha",
+ "LatentUpscaleBy",
+ "VAEEncode",
+ "LoadImage",
+ "ControlNetLoader",
+ "CLIPVisionLoader",
+ "SaveImage",
+ ",",
+ "ComfyUI Impact Pack",
+ "SAMLoader",
+ "UltralyticsDetectorProvider",
+ "FaceDetailer",
+ ","
+ ]
+ }
+ };
+
public static string CurrentDirectory => Directory.GetCurrentDirectory();
public static Indexer Types { get; } = new();
diff --git a/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs b/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs
index debbe0f3..34540a8f 100644
--- a/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs
+++ b/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs
@@ -41,6 +41,9 @@ public partial class MockInferenceClientManager : ObservableObject, IInferenceCl
public IObservableCollection Schedulers { get; } =
new ObservableCollectionExtended(ComfyScheduler.Defaults);
+ public IObservableCollection Preprocessors { get; } =
+ new ObservableCollectionExtended(ComfyAuxPreprocessor.Defaults);
+
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanUserConnect))]
private bool isConnected;
diff --git a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs
index a4a88705..64a522c5 100644
--- a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs
+++ b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs
@@ -20,17 +20,15 @@ public static class ComfyNodeBuilderExtensions
int? batchIndex = null
)
{
- var emptyLatent = builder
- .Nodes
- .AddTypedNode(
- new ComfyNodeBuilder.EmptyLatentImage
- {
- Name = "EmptyLatentImage",
- BatchSize = batchSize,
- Height = height,
- Width = width
- }
- );
+ var emptyLatent = builder.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.EmptyLatentImage
+ {
+ Name = "EmptyLatentImage",
+ BatchSize = batchSize,
+ Height = height,
+ Width = width
+ }
+ );
builder.Connections.Primary = emptyLatent.Output;
builder.Connections.PrimarySize = new Size(width, height);
@@ -39,15 +37,15 @@ public static class ComfyNodeBuilderExtensions
if (batchIndex is not null)
{
builder.Connections.Primary = builder
- .Nodes
- .AddNamedNode(
- ComfyNodeBuilder.LatentFromBatch(
- "LatentFromBatch",
- builder.GetPrimaryAsLatent(),
+ .Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LatentFromBatch
+ {
+ Name = "LatentFromBatch",
+ Samples = builder.GetPrimaryAsLatent(),
// remote expects a 0-based index, vm is 1-based
- batchIndex.Value - 1,
- 1
- )
+ BatchIndex = batchIndex.Value - 1,
+ Length = 1
+ }
)
.Output;
}
@@ -67,9 +65,9 @@ public static class ComfyNodeBuilderExtensions
var sourceImageRelativePath = Path.Combine("Inference", image.GetHashGuidFileNameCached());
// Load source
- var loadImage = builder
- .Nodes
- .AddTypedNode(new ComfyNodeBuilder.LoadImage { Name = "LoadImage", Image = sourceImageRelativePath });
+ var loadImage = builder.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LoadImage { Name = "LoadImage", Image = sourceImageRelativePath }
+ );
builder.Connections.Primary = loadImage.Output1;
builder.Connections.PrimarySize = imageSize;
@@ -78,15 +76,15 @@ public static class ComfyNodeBuilderExtensions
if (batchIndex is not null)
{
builder.Connections.Primary = builder
- .Nodes
- .AddNamedNode(
- ComfyNodeBuilder.LatentFromBatch(
- "LatentFromBatch",
- builder.GetPrimaryAsLatent(),
+ .Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LatentFromBatch
+ {
+ Name = "LatentFromBatch",
+ Samples = builder.GetPrimaryAsLatent(),
// remote expects a 0-based index, vm is 1-based
- batchIndex.Value - 1,
- 1
- )
+ BatchIndex = batchIndex.Value - 1,
+ Length = 1
+ }
)
.Output;
}
@@ -97,25 +95,24 @@ public static class ComfyNodeBuilderExtensions
if (builder.Connections.Primary is null)
throw new ArgumentException("No Primary");
- var image = builder
- .Connections
- .Primary
- .Match(
- _ =>
- builder.GetPrimaryAsImage(
- builder.Connections.PrimaryVAE
- ?? builder.Connections.Refiner.VAE
- ?? builder.Connections.Base.VAE
- ?? throw new ArgumentException("No Primary, Refiner, or Base VAE")
- ),
- image => image
- );
+ var image = builder.Connections.Primary.Match(
+ _ =>
+ builder.GetPrimaryAsImage(
+ builder.Connections.PrimaryVAE
+ ?? builder.Connections.Refiner.VAE
+ ?? builder.Connections.Base.VAE
+ ?? throw new ArgumentException("No Primary, Refiner, or Base VAE")
+ ),
+ image => image
+ );
- var previewImage = builder
- .Nodes
- .AddTypedNode(
- new ComfyNodeBuilder.PreviewImage { Name = builder.Nodes.GetUniqueName("SaveImage"), Images = image }
- );
+ var previewImage = builder.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.PreviewImage
+ {
+ Name = builder.Nodes.GetUniqueName("SaveImage"),
+ Images = image
+ }
+ );
builder.Connections.OutputNodes.Add(previewImage);
diff --git a/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs b/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
index 9c001a92..93951249 100644
--- a/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
+++ b/StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
@@ -103,4 +103,13 @@ public class FallbackRamCachedWebImageLoader : RamCachedWebImageLoader
}
}
}
+
+ public void ClearCache()
+ {
+ var cache =
+ this.GetPrivateField>>("_memoryCache")
+ ?? throw new NullReferenceException("Memory cache not found");
+
+ cache.Clear();
+ }
}
diff --git a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
index 3b16d078..0442466c 100644
--- a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
+++ b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs
@@ -83,7 +83,7 @@ public class UriHandler
[SupportedOSPlatform("windows")]
private void RegisterUriSchemeWin()
{
- using var key = Registry.CurrentUser.CreateSubKey(@$"SOFTWARE\Classes\{Scheme}");
+ using var key = Registry.CurrentUser.CreateSubKey(@$"SOFTWARE\Classes\{Scheme}", true);
key.SetValue("", "URL:" + Description);
key.SetValue(null, Description);
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
index dd2b597d..e38cda20 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
+++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
@@ -374,6 +374,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Open on OpenArt.
+ ///
+ public static string Action_OpenOnOpenArt {
+ get {
+ return ResourceManager.GetString("Action_OpenOnOpenArt", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Open Project....
///
@@ -734,6 +743,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Another instance of Stability Matrix is already running. Please close it before starting a new one..
+ ///
+ public static string Label_AnotherInstanceAlreadyRunning {
+ get {
+ return ResourceManager.GetString("Label_AnotherInstanceAlreadyRunning", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to API Key.
///
@@ -1301,6 +1319,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Error retrieving workflows.
+ ///
+ public static string Label_ErrorRetrievingWorkflows {
+ get {
+ return ResourceManager.GetString("Label_ErrorRetrievingWorkflows", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Everything looks good!.
///
@@ -1337,6 +1364,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Finished importing workflow and custom nodes.
+ ///
+ public static string Label_FinishedImportingWorkflow {
+ get {
+ return ResourceManager.GetString("Label_FinishedImportingWorkflow", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to First Page.
///
@@ -1481,6 +1517,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Infinite Scrolling.
+ ///
+ public static string Label_InfiniteScrolling {
+ get {
+ return ResourceManager.GetString("Label_InfiniteScrolling", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Inner exception.
///
@@ -1769,6 +1814,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Node Details.
+ ///
+ public static string Label_NodeDetails {
+ get {
+ return ResourceManager.GetString("Label_NodeDetails", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to No extensions found..
///
@@ -1832,6 +1886,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to OpenArt Browser.
+ ///
+ public static string Label_OpenArtBrowser {
+ get {
+ return ResourceManager.GetString("Label_OpenArtBrowser", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Output Folder.
///
@@ -2273,6 +2336,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Stability Matrix is already running.
+ ///
+ public static string Label_StabilityMatrixAlreadyRunning {
+ get {
+ return ResourceManager.GetString("Label_StabilityMatrixAlreadyRunning", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Steps.
///
@@ -2543,6 +2615,69 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Workflow Browser.
+ ///
+ public static string Label_WorkflowBrowser {
+ get {
+ return ResourceManager.GetString("Label_WorkflowBrowser", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Workflow Deleted.
+ ///
+ public static string Label_WorkflowDeleted {
+ get {
+ return ResourceManager.GetString("Label_WorkflowDeleted", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} deleted successfully.
+ ///
+ public static string Label_WorkflowDeletedSuccessfully {
+ get {
+ return ResourceManager.GetString("Label_WorkflowDeletedSuccessfully", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Workflow Description.
+ ///
+ public static string Label_WorkflowDescription {
+ get {
+ return ResourceManager.GetString("Label_WorkflowDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The workflow and custom nodes have been imported..
+ ///
+ public static string Label_WorkflowImportComplete {
+ get {
+ return ResourceManager.GetString("Label_WorkflowImportComplete", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Workflow Imported.
+ ///
+ public static string Label_WorkflowImported {
+ get {
+ return ResourceManager.GetString("Label_WorkflowImported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Workflows.
+ ///
+ public static string Label_Workflows {
+ get {
+ return ResourceManager.GetString("Label_Workflows", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to You're up to date.
///
@@ -2624,6 +2759,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Installed Workflows.
+ ///
+ public static string TabLabel_InstalledWorkflows {
+ get {
+ return ResourceManager.GetString("TabLabel_InstalledWorkflows", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Add a package to get started!.
///
@@ -2651,6 +2795,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Click here to review prompt syntax and how to include Lora / Embeddings..
+ ///
+ public static string TeachingTip_InferencePromptHelpButton {
+ get {
+ return ResourceManager.GetString("TeachingTip_InferencePromptHelpButton", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here.
///
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.es.resx b/StabilityMatrix.Avalonia/Languages/Resources.es.resx
index 5efb7a15..28b0dae1 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.es.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.es.resx
@@ -963,4 +963,66 @@
Mientras se instala tu paquete, aquí hay algunos modelos que recomendamos para ayudarte a comenzar.
+
+ Notificaciones
+
+
+ Ninguno
+
+
+ Se Requiere ComfyUI
+
+
+ Se requiere ComfyUI para instalar este paquete. ¿Quieres instalarlo ahora?
+
+
+ Por favor, seleccione una ubicación de descarga.
+
+
+ Seleccione la Ubicación de Descarga:
+
+
+ Configuración
+ This is used in inference when models have a yaml config file
+
+
+ Desplazamiento Automático hasta el Final
+
+
+ Confirmar Salida
+
+
+ ¿Seguro que quieres salir? Esto también cerrará cualquier paquete que se esté ejecutando actualmente.
+
+
+ Consola
+
+
+ Interfaz Web
+ This will be used on a button to launch the web ui
+
+
+ Paquetes
+
+
+ Esta acción no se puede deshacer.
+
+
+ ¿Estás seguro de que deseas eliminar {0} imágenes?
+
+
+ Estamos verificando algunas especificaciones de hardware para determinar la compatibilidad.
+
+
+ ¡Todo parece estar bien!
+
+
+ Recomendamos una GPU con soporte CUDA para obtener la mejor experiencia. Puedes continuar sin una, pero es posible que algunos paquetes no funcionen y la inferencia pueda ser más lenta.
+
+
+ Checkpoints
+
+
+ Navegador de Modelos
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx b/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
index 6e195f4d..f205b67c 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
@@ -598,7 +598,7 @@
Informations sur la version de Python
- Redémarrage
+ Redémarrer
Confirmer la suppression
@@ -930,4 +930,52 @@
Pendant que votre paquet s'installe, voici quelques modèles que nous recommandons pour vous aidez à démarrer.
+
+ Notifications
+
+
+ Aucun
+
+
+ ComfyUI est requis
+
+
+ ComfyUI est requis pour installer ce paquet. Voulez vous l'installer maintenant ?
+
+
+ Merci de sélectionner un dossier de téléchargement.
+
+
+ Sélectionner un dossier de téléchargement:
+
+
+ Défiler automatiquement à la fin
+
+
+ Confirmer la sortie
+
+
+ Êtes vous certain de vouloir quitter ? Cela va fermer toutes les instances actuellement lancées.
+
+
+ Console
+
+
+ Paquets
+
+
+ Cette action ne peut être annulée.
+
+
+ Êtes vous certain de vouloir supprimer les {0} images?
+
+
+ Nous vérifions quelques spécifications matérielles pour vérifier la compatibilité.
+
+
+ Tout à l'air bon!
+
+
+ Nous recommandons un GPU avec prise en charge CUDA pour une meilleure expérience. Vous pouvez continuer sans en avoir un, mais certains packages peuvent ne pas fonctionner et l'inférence peut être plus lente.
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx
index 9f35cbfa..ee360cb1 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.resx
@@ -972,9 +972,30 @@
Select Download Location:
+
+ Workflows
+
+
+ Infinite Scrolling
+
+
+ Workflow Browser
+
Config
+
+ Open on OpenArt
+
+
+ Node Details
+
+
+ Workflow Description
+
+
+ OpenArt Browser
+
Preview Preprocessor
@@ -1020,4 +1041,34 @@
The 'Open Web UI' button has moved to the command bar
+
+ Another instance of Stability Matrix is already running. Please close it before starting a new one.
+
+
+ Stability Matrix is already running
+
+
+ {0} deleted successfully
+
+
+ Workflow Deleted
+
+
+ Error retrieving workflows
+
+
+ Installed Workflows
+
+
+ Workflow Imported
+
+
+ Finished importing workflow and custom nodes
+
+
+ The workflow and custom nodes have been imported.
+
+
+ Click here to review prompt syntax and how to include Lora / Embeddings.
+
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx b/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
index 8d2e1d7f..5b6064d2 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
@@ -961,4 +961,64 @@
Paketiniz yüklenirken başlamanıza yardımcı olması için önerdiğimiz bazı modeller aşağıda verilmiştir.
+
+ Bildirimler
+
+
+ Hiçbiri
+
+
+ ComfyUI Gerekli
+
+
+ Bu paketi kurmak için ComfyUI gereklidir. Şimdi yüklemek ister misiniz?
+
+
+ Lütfen bir indirme konumu seçin.
+
+
+ İndirme Konumunu Seçin:
+
+
+ Yapılandırma
+
+
+ Otomatik Sona Kaydır
+
+
+ Çıkışı Onayla
+
+
+ Çıkmak istediğine emin misin? Bu aynı zamanda şu anda çalışan tüm paketleri de kapatacaktır.
+
+
+ Konsol
+
+
+ Web arayüzü
+
+
+ Paketler
+
+
+ Bu eylem geri alınamaz.
+
+
+ {0} görseli silmek istediğinizden emin misiniz?
+
+
+ Uyumluluğu belirlemek için bazı donanım özelliklerini kontrol ediyoruz.
+
+
+ Her şey iyi gözüküyor!
+
+
+ En iyi deneyim için CUDA destekli bir GPU öneriyoruz. Olmadan da devam edebilirsiniz ancak bazı paketler çalışmayabilir ve çıkarımlar daha yavaş olabilir.
+
+
+ Kontrol noktaları
+
+
+ Model Tarayıcı
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Models/ImageSource.cs b/StabilityMatrix.Avalonia/Models/ImageSource.cs
index 973b0bf6..331a9c09 100644
--- a/StabilityMatrix.Avalonia/Models/ImageSource.cs
+++ b/StabilityMatrix.Avalonia/Models/ImageSource.cs
@@ -58,6 +58,9 @@ public record ImageSource : IDisposable, ITemplateKey
Bitmap = bitmap;
}
+ [JsonIgnore]
+ public Uri? Uri => LocalFile?.FullPath != null ? new Uri(LocalFile.FullPath) : RemoteUrl;
+
///
public ImageSourceTemplateType TemplateKey { get; private set; }
diff --git a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
index 375ca17f..487d39b7 100644
--- a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
+++ b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
@@ -2,9 +2,11 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Hashing;
+using System.Linq;
using System.Text;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+using StabilityMatrix.Core.Models.Inference;
namespace StabilityMatrix.Avalonia.Models.Inference;
@@ -17,7 +19,7 @@ public class ModuleApplyStepEventArgs : EventArgs
public NodeDictionary Nodes => Builder.Nodes;
- public ModuleApplyStepTemporaryArgs Temp { get; } = new();
+ public ModuleApplyStepTemporaryArgs Temp { get; set; } = new();
///
/// Generation overrides (like hires fix generate, current seed generate, etc.)
@@ -26,6 +28,35 @@ public class ModuleApplyStepEventArgs : EventArgs
public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = [];
+ public List> PreOutputActions { get; init; } = [];
+
+ public void InvokeAllPreOutputActions()
+ {
+ foreach (var action in PreOutputActions)
+ {
+ action(this);
+ }
+ }
+
+ ///
+ /// Creates a new with the given .
+ ///
+ ///
+ public ModuleApplyStepTemporaryArgs CreateTempFromBuilder()
+ {
+ return new ModuleApplyStepTemporaryArgs
+ {
+ Primary = Builder.Connections.Primary,
+ PrimaryVAE = Builder.Connections.PrimaryVAE,
+ Models = new Dictionary(
+ Builder.Connections.Models.ToDictionary(
+ pair => pair.Key,
+ pair => new ModelConnections(pair.Value)
+ )
+ )
+ };
+ }
+
public void AddFileTransfer(string sourcePath, string destinationRelativePath)
{
FilesToTransfer.Add((sourcePath, destinationRelativePath));
@@ -54,22 +85,4 @@ public class ModuleApplyStepEventArgs : EventArgs
return destPath;
}
-
- public class ModuleApplyStepTemporaryArgs
- {
- ///
- /// Temporary conditioning apply step, used by samplers to apply control net.
- ///
- public ConditioningConnections? Conditioning { get; set; }
-
- ///
- /// Temporary refiner conditioning apply step, used by samplers to apply control net.
- ///
- public ConditioningConnections? RefinerConditioning { get; set; }
-
- ///
- /// Temporary model apply step, used by samplers to apply control net.
- ///
- public ModelNodeConnection? Model { get; set; }
- }
}
diff --git a/StabilityMatrix.Avalonia/Models/OpenArtCustomNode.cs b/StabilityMatrix.Avalonia/Models/OpenArtCustomNode.cs
new file mode 100644
index 00000000..b075758d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/OpenArtCustomNode.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace StabilityMatrix.Avalonia.Models;
+
+public class OpenArtCustomNode
+{
+ public required string Title { get; set; }
+ public List Children { get; set; } = [];
+ public bool IsInstalled { get; set; }
+}
diff --git a/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs b/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
new file mode 100644
index 00000000..7620816c
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+using Avalonia.Platform.Storage;
+using StabilityMatrix.Core.Models.Api.OpenArt;
+
+namespace StabilityMatrix.Avalonia.Models;
+
+public class OpenArtMetadata
+{
+ [JsonPropertyName("sm_workflow_data")]
+ public OpenArtSearchResult? Workflow { get; set; }
+
+ [JsonIgnore]
+ public string? FirstThumbnail => Workflow?.Thumbnails?.Select(x => x.Url).FirstOrDefault()?.ToString();
+
+ [JsonIgnore]
+ public List? FilePath { get; set; }
+
+ [JsonIgnore]
+ public bool HasMetadata => Workflow?.Creator != null;
+}
diff --git a/StabilityMatrix.Avalonia/Models/PackageOutputCategory.cs b/StabilityMatrix.Avalonia/Models/PackageOutputCategory.cs
index 2318d0f8..62b69917 100644
--- a/StabilityMatrix.Avalonia/Models/PackageOutputCategory.cs
+++ b/StabilityMatrix.Avalonia/Models/PackageOutputCategory.cs
@@ -1,7 +1,10 @@
-namespace StabilityMatrix.Avalonia.Models;
+using System.Collections.ObjectModel;
+
+namespace StabilityMatrix.Avalonia.Models;
public class PackageOutputCategory
{
+ public ObservableCollection SubDirectories { get; set; } = new();
public required string Name { get; set; }
public required string Path { get; set; }
}
diff --git a/StabilityMatrix.Avalonia/Models/PackageSteps/UnpackSiteCustomizeStep.cs b/StabilityMatrix.Avalonia/Models/PackageSteps/UnpackSiteCustomizeStep.cs
new file mode 100644
index 00000000..92e5f413
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/PackageSteps/UnpackSiteCustomizeStep.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Threading.Tasks;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Models.PackageModification;
+using StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Python;
+
+namespace StabilityMatrix.Avalonia.Models.PackageSteps;
+
+public class UnpackSiteCustomizeStep(DirectoryPath venvPath) : IPackageStep
+{
+ public async Task ExecuteAsync(IProgress? progress = null)
+ {
+ var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath);
+ var file = sitePackages.JoinFile("sitecustomize.py");
+ file.Directory?.Create();
+ await Assets.PyScriptSiteCustomize.ExtractTo(file);
+ }
+
+ public string ProgressTitle => "Unpacking prerequisites...";
+}
diff --git a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs
index fc134c74..75f83447 100644
--- a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs
+++ b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs
@@ -44,6 +44,7 @@ public interface IInferenceClientManager : IDisposable, INotifyPropertyChanged,
IObservableCollection Samplers { get; }
IObservableCollection Upscalers { get; }
IObservableCollection Schedulers { get; }
+ IObservableCollection Preprocessors { get; }
Task CopyImageToInputAsync(FilePath imageFile, CancellationToken cancellationToken = default);
diff --git a/StabilityMatrix.Avalonia/Services/IModelDownloadLinkHandler.cs b/StabilityMatrix.Avalonia/Services/IModelDownloadLinkHandler.cs
new file mode 100644
index 00000000..dfea3c3f
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Services/IModelDownloadLinkHandler.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace StabilityMatrix.Avalonia.Services;
+
+public interface IModelDownloadLinkHandler
+{
+ Task StartListening();
+}
diff --git a/StabilityMatrix.Avalonia/Services/INavigationService.cs b/StabilityMatrix.Avalonia/Services/INavigationService.cs
index f1aa8ba4..68f7f261 100644
--- a/StabilityMatrix.Avalonia/Services/INavigationService.cs
+++ b/StabilityMatrix.Avalonia/Services/INavigationService.cs
@@ -37,4 +37,6 @@ public interface INavigationService<[SuppressMessage("ReSharper", "UnusedTypePar
void NavigateTo(ViewModelBase viewModel, NavigationTransitionInfo? transitionInfo = null);
bool GoBack();
+
+ bool CanGoBack { get; }
}
diff --git a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
index 5ad092af..22782abb 100644
--- a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
+++ b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
@@ -14,6 +14,7 @@ using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.TagCompletion;
using StabilityMatrix.Core.Api;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Inference;
using StabilityMatrix.Core.Models;
@@ -101,6 +102,11 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
public IObservableCollection Schedulers { get; } =
new ObservableCollectionExtended();
+ public IObservableCollection Preprocessors { get; } =
+ new ObservableCollectionExtended();
+
+ private readonly SourceCache preprocessorsSource = new(p => p.Value);
+
public InferenceClientManager(
ILogger logger,
IApiFactory apiFactory,
@@ -166,6 +172,8 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
schedulersSource.Connect().DeferUntilLoaded().Bind(Schedulers).Subscribe();
+ preprocessorsSource.Connect().DeferUntilLoaded().Bind(Preprocessors).Subscribe();
+
settingsManager.RegisterOnLibraryDirSet(_ =>
{
Dispatcher.UIThread.Post(ResetSharedProperties, DispatcherPriority.Background);
@@ -270,6 +278,15 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
});
logger.LogTrace("Loaded scheduler methods: {@Schedulers}", schedulerNames);
}
+
+ // Add preprocessor names from Inference_Core_AIO_Preprocessor node (might not exist if no extension)
+ if (
+ await Client.GetOptionalNodeOptionNamesAsync("Inference_Core_AIO_Preprocessor", "preprocessor") is
+ { } preprocessorNames
+ )
+ {
+ preprocessorsSource.EditDiff(preprocessorNames.Select(n => new ComfyAuxPreprocessor(n)));
+ }
}
///
@@ -342,6 +359,9 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
u => !modelUpscalersSource.Lookup(u.Name).HasValue
);
downloadableUpscalersSource.EditDiff(remoteUpscalers, ComfyUpscaler.Comparer);
+
+ // Default Preprocessors
+ preprocessorsSource.EditDiff(ComfyAuxPreprocessor.Defaults);
}
///
diff --git a/StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs b/StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs
new file mode 100644
index 00000000..62145809
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs
@@ -0,0 +1,251 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Web;
+using Avalonia.Controls.Notifications;
+using Avalonia.Threading;
+using MessagePipe;
+using Microsoft.Extensions.Logging;
+using StabilityMatrix.Avalonia.Helpers;
+using StabilityMatrix.Core.Api;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Avalonia.Services;
+
+[Singleton(typeof(IModelDownloadLinkHandler)), Singleton(typeof(IAsyncDisposable))]
+public class ModelDownloadLinkHandler(
+ IDistributedSubscriber uriHandlerSubscriber,
+ ILogger logger,
+ ICivitApi civitApi,
+ INotificationService notificationService,
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ ITrackedDownloadService trackedDownloadService
+) : IAsyncDisposable, IModelDownloadLinkHandler
+{
+ private IAsyncDisposable? uriHandlerSubscription;
+ private const string DownloadCivitModel = "downloadCivitModel";
+
+ public async Task StartListening()
+ {
+ uriHandlerSubscription = await uriHandlerSubscriber.SubscribeAsync(
+ UriHandler.IpcKeySend,
+ UriReceivedHandler
+ );
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (uriHandlerSubscription is not null)
+ {
+ await uriHandlerSubscription.DisposeAsync();
+ uriHandlerSubscription = null;
+ }
+ }
+
+ private void UriReceivedHandler(Uri receivedUri)
+ {
+ logger.LogDebug("ModelDownloadLinkHandler Received URI: {Uri}", receivedUri.PathAndQuery);
+ if (!receivedUri.Host.Equals(DownloadCivitModel, StringComparison.OrdinalIgnoreCase))
+ return;
+
+ var queryDict = HttpUtility.ParseQueryString(receivedUri.Query);
+ var modelIdStr = queryDict["modelId"];
+ var modelVersionIdStr = queryDict["modelVersionId"];
+ var type = queryDict["type"];
+ var format = queryDict["format"];
+ var size = queryDict["size"];
+ var fp = queryDict["fp"];
+
+ if (
+ string.IsNullOrWhiteSpace(modelIdStr)
+ || string.IsNullOrWhiteSpace(type)
+ || string.IsNullOrWhiteSpace(format)
+ || !int.TryParse(modelIdStr, out var modelId)
+ || !Enum.TryParse(type, out var civitFileType)
+ || !Enum.TryParse(format, out var civitFormat)
+ )
+ {
+ logger.LogError("ModelDownloadLinkHandler: Invalid query parameters");
+
+ Dispatcher.UIThread.Post(
+ () =>
+ notificationService.Show(
+ new Notification(
+ "Invalid Download Link",
+ "The download link is invalid",
+ NotificationType.Error
+ )
+ )
+ );
+ return;
+ }
+
+ Dispatcher.UIThread.Post(
+ () =>
+ notificationService.Show(
+ "Link Received",
+ "Successfully received download link",
+ NotificationType.Warning
+ )
+ );
+
+ var modelTask = civitApi.GetModelById(modelId);
+ modelTask.Wait();
+ var model = modelTask.Result;
+
+ var useModelVersion = !string.IsNullOrWhiteSpace(modelVersionIdStr);
+ var modelVersionId = useModelVersion ? int.Parse(modelVersionIdStr) : 0;
+
+ var modelVersion = useModelVersion
+ ? model.ModelVersions?.FirstOrDefault(x => x.Id == modelVersionId)
+ : model.ModelVersions?.FirstOrDefault();
+
+ if (modelVersion is null)
+ {
+ logger.LogError("ModelDownloadLinkHandler: Model version not found");
+ Dispatcher.UIThread.Post(
+ () =>
+ notificationService.Show(
+ new Notification(
+ "Model has no versions available",
+ "This model has no versions available for download",
+ NotificationType.Error
+ )
+ )
+ );
+ return;
+ }
+
+ var possibleFiles = modelVersion.Files?.Where(
+ x => x.Type == civitFileType && x.Metadata.Format == civitFormat
+ );
+
+ if (!string.IsNullOrWhiteSpace(fp) && Enum.TryParse(fp, out var fpType))
+ {
+ possibleFiles = possibleFiles?.Where(x => x.Metadata.Fp == fpType);
+ }
+
+ if (!string.IsNullOrWhiteSpace(size) && Enum.TryParse(size, out var modelSize))
+ {
+ possibleFiles = possibleFiles?.Where(x => x.Metadata.Size == modelSize);
+ }
+
+ possibleFiles = possibleFiles?.ToList();
+
+ if (possibleFiles is null)
+ {
+ Dispatcher.UIThread.Post(
+ () =>
+ notificationService.Show(
+ new Notification(
+ "Model has no files available",
+ "This model has no files available for download",
+ NotificationType.Error
+ )
+ )
+ );
+ logger.LogError("ModelDownloadLinkHandler: Model file not found");
+ return;
+ }
+
+ var selectedFile = possibleFiles.FirstOrDefault() ?? modelVersion.Files?.FirstOrDefault();
+
+ var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);
+ var downloadDirectory = rootModelsDirectory.JoinDir(
+ selectedFile.Type == CivitFileType.VAE
+ ? SharedFolderType.VAE.GetStringValue()
+ : model.Type.ConvertTo().GetStringValue()
+ );
+
+ downloadDirectory.Create();
+ var downloadPath = downloadDirectory.JoinFile(selectedFile.Name);
+
+ // Create tracked download
+ var download = trackedDownloadService.NewDownload(selectedFile.DownloadUrl, downloadPath);
+
+ // Download model info and preview first
+ var saveCmInfoTask = SaveCmInfo(model, modelVersion, selectedFile, downloadDirectory);
+ var savePreviewImageTask = SavePreviewImage(modelVersion, downloadPath);
+
+ Task.WaitAll([saveCmInfoTask, savePreviewImageTask]);
+
+ var cmInfoPath = saveCmInfoTask.Result;
+ var previewImagePath = savePreviewImageTask.Result;
+
+ // Add hash info
+ download.ExpectedHashSha256 = selectedFile.Hashes.SHA256;
+
+ // Add files to cleanup list
+ download.ExtraCleanupFileNames.Add(cmInfoPath);
+ if (previewImagePath is not null)
+ {
+ download.ExtraCleanupFileNames.Add(previewImagePath);
+ }
+
+ // Add hash context action
+ download.ContextAction = CivitPostDownloadContextAction.FromCivitFile(selectedFile);
+
+ download.Start();
+
+ Dispatcher.UIThread.Post(
+ () => notificationService.Show("Download Started", $"Downloading {selectedFile.Name}")
+ );
+ }
+
+ private static async Task SaveCmInfo(
+ CivitModel model,
+ CivitModelVersion modelVersion,
+ CivitFile modelFile,
+ DirectoryPath downloadDirectory
+ )
+ {
+ var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Name);
+ var modelInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTime.UtcNow);
+
+ await modelInfo.SaveJsonToDirectory(downloadDirectory, modelFileName);
+
+ var jsonName = $"{modelFileName}.cm-info.json";
+ return downloadDirectory.JoinFile(jsonName);
+ }
+
+ ///
+ /// Saves the preview image to the same directory as the model file
+ ///
+ ///
+ ///
+ /// The file path of the saved preview image
+ private async Task SavePreviewImage(CivitModelVersion modelVersion, FilePath modelFilePath)
+ {
+ // Skip if model has no images
+ if (modelVersion.Images == null || modelVersion.Images.Count == 0)
+ {
+ return null;
+ }
+
+ var image = modelVersion.Images.FirstOrDefault(x => x.Type == "image");
+ if (image is null)
+ return null;
+
+ var imageExtension = Path.GetExtension(image.Url).TrimStart('.');
+ if (imageExtension is "jpg" or "jpeg" or "png")
+ {
+ var imageDownloadPath = modelFilePath.Directory!.JoinFile(
+ $"{modelFilePath.NameWithoutExtension}.preview.{imageExtension}"
+ );
+
+ var imageTask = downloadService.DownloadToFileAsync(image.Url, imageDownloadPath);
+ await notificationService.TryAsync(imageTask, "Could not download preview image");
+
+ return imageDownloadPath;
+ }
+
+ return null;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Services/NavigationService.cs b/StabilityMatrix.Avalonia/Services/NavigationService.cs
index c95d3068..8778f7ca 100644
--- a/StabilityMatrix.Avalonia/Services/NavigationService.cs
+++ b/StabilityMatrix.Avalonia/Services/NavigationService.cs
@@ -188,4 +188,6 @@ public class NavigationService : INavigationService
_frame.GoBack();
return true;
}
+
+ public bool CanGoBack => _frame?.CanGoBack ?? false;
}
diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs
new file mode 100644
index 00000000..38db4dc3
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls.Notifications;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.Extensions.Logging;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.ViewModels;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Helper.Factory;
+using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Models.Packages;
+using StabilityMatrix.Core.Python;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Avalonia.Services;
+
+[Singleton]
+public partial class RunningPackageService(
+ ILogger logger,
+ IPackageFactory packageFactory,
+ INotificationService notificationService,
+ ISettingsManager settingsManager,
+ IPyRunner pyRunner
+) : ObservableObject
+{
+ // 🤔 what if we put the ConsoleViewModel inside the BasePackage? 🤔
+ [ObservableProperty]
+ private ObservableDictionary runningPackages = [];
+
+ public async Task StartPackage(InstalledPackage installedPackage, string? command = null)
+ {
+ var activeInstallName = installedPackage.PackageName;
+ var basePackage = string.IsNullOrWhiteSpace(activeInstallName)
+ ? null
+ : packageFactory.GetNewBasePackage(installedPackage);
+
+ if (basePackage == null)
+ {
+ logger.LogWarning(
+ "During launch, package name '{PackageName}' did not match a definition",
+ activeInstallName
+ );
+
+ notificationService.Show(
+ new Notification(
+ "Package name invalid",
+ "Install package name did not match a definition. Please reinstall and let us know about this issue.",
+ NotificationType.Error
+ )
+ );
+ return null;
+ }
+
+ // If this is the first launch (LaunchArgs is null),
+ // load and save a launch options dialog vm
+ // so that dynamic initial values are saved.
+ if (installedPackage.LaunchArgs == null)
+ {
+ var definitions = basePackage.LaunchOptions;
+ // Create config cards and save them
+ var cards = LaunchOptionCard
+ .FromDefinitions(definitions, Array.Empty())
+ .ToImmutableArray();
+
+ var args = cards.SelectMany(c => c.Options).ToList();
+
+ logger.LogDebug(
+ "Setting initial launch args: {Args}",
+ string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr()))
+ );
+
+ settingsManager.SaveLaunchArgs(installedPackage.Id, args);
+ }
+
+ if (basePackage is not StableSwarm)
+ {
+ await pyRunner.Initialize();
+ }
+
+ // Get path from package
+ var packagePath = new DirectoryPath(settingsManager.LibraryDir, installedPackage.LibraryPath!);
+
+ if (basePackage is not StableSwarm)
+ {
+ // Unpack sitecustomize.py to venv
+ await UnpackSiteCustomize(packagePath.JoinDir("venv"));
+ }
+
+ // Clear console and start update processing
+ var console = new ConsoleViewModel();
+ console.StartUpdates();
+
+ // Update shared folder links (in case library paths changed)
+ await basePackage.UpdateModelFolders(
+ packagePath,
+ installedPackage.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod
+ );
+
+ if (installedPackage.UseSharedOutputFolder)
+ {
+ await basePackage.SetupOutputFolderLinks(installedPackage.FullPath!);
+ }
+
+ // Load user launch args from settings and convert to string
+ var userArgs = installedPackage.LaunchArgs ?? [];
+ var userArgsString = string.Join(" ", userArgs.Select(opt => opt.ToArgString()));
+
+ // Join with extras, if any
+ userArgsString = string.Join(" ", userArgsString, basePackage.ExtraLaunchArguments);
+
+ // Use input command if provided, otherwise use package launch command
+ command ??= basePackage.LaunchCommand;
+
+ await basePackage.RunPackage(packagePath, command, userArgsString, o => console.Post(o));
+ var runningPackage = new PackagePair(installedPackage, basePackage);
+
+ var viewModel = new RunningPackageViewModel(
+ settingsManager,
+ notificationService,
+ this,
+ runningPackage,
+ console
+ );
+ RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel);
+
+ return runningPackage;
+ }
+
+ public async Task StopPackage(Guid id)
+ {
+ if (RunningPackages.TryGetValue(id, out var vm))
+ {
+ var runningPackage = vm.RunningPackage;
+ await runningPackage.BasePackage.WaitForShutdown();
+ RunningPackages.Remove(id);
+ }
+ }
+
+ public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) =>
+ RunningPackages.TryGetValue(id, out var vm) ? vm : null;
+
+ private static async Task UnpackSiteCustomize(DirectoryPath venvPath)
+ {
+ var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath);
+ var file = sitePackages.JoinFile("sitecustomize.py");
+ file.Directory?.Create();
+ await Assets.PyScriptSiteCustomize.ExtractTo(file, true);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
index d1055205..b3fba009 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
@@ -39,16 +39,17 @@
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
+
+
@@ -57,10 +58,10 @@
-
-
-
-
+
+
+
+
@@ -77,25 +78,25 @@
-
+
-
+
-
+
-
-
+
+
-
+
@@ -197,6 +198,9 @@
NewInstallerDialog.axaml
Code
+
+ MSBuild:GenerateCodeFromAttributes
+
diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings
index ef254925..524abe98 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings
@@ -2,4 +2,5 @@
Yes
Pessimistic
UI
- True
+ True
+ True
diff --git a/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml b/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml
index b0af1750..0b8d75f8 100644
--- a/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml
+++ b/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml
@@ -99,6 +99,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
index 61d77d82..c1274ef4 100644
--- a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
+++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
@@ -6,7 +6,9 @@
xmlns:mocks="using:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
- xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
+ xmlns:vendorLabs="clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs">
@@ -42,15 +44,14 @@
ColumnSpacing="6"
RowSpacing="0">
-
+ Stretch="UniformToFill"/>
-
-
-
-
-
+ Stretch="UniformToFill"/>
-
-
-
-
-
+ Stretch="UniformToFill">
+
#00BCD4
#009688
#2C582C
+ #2C582C
#3A783C
#4BA04F
#AA4BA04F
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index 2d70b02e..d445488d 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -16,10 +16,9 @@ using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using ExifLibrary;
using FluentAvalonia.UI.Controls;
-using Microsoft.Extensions.DependencyInjection;
-using Nito.Disposables.Internals;
using NLog;
using Refit;
+using Semver;
using SkiaSharp;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Helpers;
@@ -59,6 +58,7 @@ public abstract partial class InferenceGenerationViewModelBase
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly ISettingsManager settingsManager;
+ private readonly RunningPackageService runningPackageService;
private readonly INotificationService notificationService;
private readonly ServiceManager vmFactory;
@@ -79,12 +79,14 @@ public abstract partial class InferenceGenerationViewModelBase
ServiceManager vmFactory,
IInferenceClientManager inferenceClientManager,
INotificationService notificationService,
- ISettingsManager settingsManager
+ ISettingsManager settingsManager,
+ RunningPackageService runningPackageService
)
: base(notificationService)
{
this.notificationService = notificationService;
this.settingsManager = settingsManager;
+ this.runningPackageService = runningPackageService;
this.vmFactory = vmFactory;
ClientManager = inferenceClientManager;
@@ -259,6 +261,30 @@ public abstract partial class InferenceGenerationViewModelBase
}
}
+ public async Task RunCustomGeneration(
+ InferenceQueueCustomPromptEventArgs args,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (ClientManager.Client is not { } client)
+ {
+ throw new InvalidOperationException("Client is not connected");
+ }
+
+ var generationArgs = new ImageGenerationEventArgs
+ {
+ Client = client,
+ Nodes = args.Builder.ToNodeDictionary(),
+ OutputNodeNames = args.Builder.Connections.OutputNodeNames.ToArray(),
+ Project = InferenceProjectDocument.FromLoadable(this),
+ FilesToTransfer = args.FilesToTransfer,
+ Parameters = new GenerationParameters(),
+ ClearOutputImages = true
+ };
+
+ await RunGeneration(generationArgs, cancellationToken);
+ }
+
///
/// Runs a generation task
///
@@ -643,12 +669,10 @@ public abstract partial class InferenceGenerationViewModelBase
{
// Get prompt required extensions
// Just static for now but could do manifest lookup when we support custom workflows
- var requiredExtensions = nodeDictionary
- .ClassTypeRequiredExtensions.Values.SelectMany(x => x)
- .ToHashSet();
+ var requiredExtensionSpecifiers = nodeDictionary.RequiredExtensions.ToList();
// Skip if no extensions required
- if (requiredExtensions.Count == 0)
+ if (requiredExtensionSpecifiers.Count == 0)
{
return true;
}
@@ -661,20 +685,63 @@ public abstract partial class InferenceGenerationViewModelBase
await ((GitPackageExtensionManager)manager).GetInstalledExtensionsLiteAsync(
localPackagePair.InstalledPackage
)
- ).ToImmutableArray();
+ ).ToList();
+
+ var localExtensionsByGitUrl = localExtensions
+ .Where(ext => ext.GitRepositoryUrl is not null)
+ .ToDictionary(ext => ext.GitRepositoryUrl!, ext => ext);
+
+ var requiredExtensionReferences = requiredExtensionSpecifiers
+ .Select(specifier => specifier.Name)
+ .ToHashSet();
- var missingExtensions = requiredExtensions
- .Except(localExtensions.Select(ext => ext.GitRepositoryUrl).WhereNotNull())
- .ToImmutableArray();
+ var missingExtensions = new List();
+ var outOfDateExtensions =
+ new List<(ExtensionSpecifier Specifier, InstalledPackageExtension Installed)>();
- if (missingExtensions.Length == 0)
+ // Check missing extensions and out of date extensions
+ foreach (var specifier in requiredExtensionSpecifiers)
+ {
+ if (!localExtensionsByGitUrl.TryGetValue(specifier.Name, out var localExtension))
+ {
+ missingExtensions.Add(specifier);
+ continue;
+ }
+
+ // Check if constraint is specified
+ if (specifier.Constraint is not null && specifier.TryGetSemVersionRange(out var semVersionRange))
+ {
+ // Get version to compare
+ localExtension = await manager.GetInstalledExtensionInfoAsync(localExtension);
+
+ // Try to parse local tag to semver
+ if (
+ localExtension.Version?.Tag is not null
+ && SemVersion.TryParse(
+ localExtension.Version.Tag,
+ SemVersionStyles.AllowV,
+ out var localSemVersion
+ )
+ )
+ {
+ // Check if not satisfied
+ if (!semVersionRange.Contains(localSemVersion))
+ {
+ outOfDateExtensions.Add((specifier, localExtension));
+ }
+ }
+ }
+ }
+
+ if (missingExtensions.Count == 0 && outOfDateExtensions.Count == 0)
{
return true;
}
var dialog = DialogHelper.CreateMarkdownDialog(
$"#### The following extensions are required for this workflow:\n"
- + $"{string.Join("\n- ", missingExtensions)}",
+ + $"{string.Join("\n- ", missingExtensions.Select(ext => ext.Name))}"
+ + $"{string.Join("\n- ", outOfDateExtensions.Select(pair => $"{pair.Item1.Name} {pair.Specifier.Constraint} {pair.Specifier.Version} (Current Version: {pair.Installed.Version?.Tag})"))}",
"Install Required Extensions?"
);
@@ -692,13 +759,14 @@ public abstract partial class InferenceGenerationViewModelBase
var steps = new List();
- foreach (var missingExtensionUrl in missingExtensions)
+ // Add install for missing extensions
+ foreach (var missingExtension in missingExtensions)
{
- if (!manifestExtensionsMap.TryGetValue(missingExtensionUrl, out var extension))
+ if (!manifestExtensionsMap.TryGetValue(missingExtension.Name, out var extension))
{
Logger.Warn(
"Extension {MissingExtensionUrl} not found in manifests",
- missingExtensionUrl
+ missingExtension.Name
);
continue;
}
@@ -706,6 +774,18 @@ public abstract partial class InferenceGenerationViewModelBase
steps.Add(new InstallExtensionStep(manager, localPackagePair.InstalledPackage, extension));
}
+ // Add update for out of date extensions
+ foreach (var (specifier, installed) in outOfDateExtensions)
+ {
+ if (!manifestExtensionsMap.TryGetValue(specifier.Name, out var extension))
+ {
+ Logger.Warn("Extension {MissingExtensionUrl} not found in manifests", specifier.Name);
+ continue;
+ }
+
+ steps.Add(new UpdateExtensionStep(manager, localPackagePair.InstalledPackage, installed));
+ }
+
var runner = new PackageModificationRunner
{
ShowDialogOnStart = true,
@@ -722,15 +802,12 @@ public abstract partial class InferenceGenerationViewModelBase
return;
// Restart Package
- // TODO: This should be handled by some DI package manager service
- var launchPage = App.Services.GetRequiredService();
-
try
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
- await launchPage.Stop();
- await launchPage.LaunchAsync();
+ await runningPackageService.StopPackage(localPackagePair.InstalledPackage.Id);
+ await runningPackageService.StartPackage(localPackagePair.InstalledPackage);
});
}
catch (Exception e)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs
index 63e5b131..57e6bf47 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs
@@ -98,11 +98,7 @@ public abstract partial class InferenceTabViewModelBase
protected async Task SaveViewState()
{
var eventArgs = new SaveViewStateEventArgs();
- saveViewStateRequestedEventManager?.RaiseEvent(
- this,
- eventArgs,
- nameof(SaveViewStateRequested)
- );
+ saveViewStateRequestedEventManager?.RaiseEvent(this, eventArgs, nameof(SaveViewStateRequested));
if (eventArgs.StateTask is not { } stateTask)
{
@@ -128,7 +124,7 @@ public abstract partial class InferenceTabViewModelBase
// TODO: Dock reset not working, using this hack for now to get a new view
var navService = App.Services.GetRequiredService>();
- navService.NavigateTo(new SuppressNavigationTransitionInfo());
+ navService.NavigateTo(new SuppressNavigationTransitionInfo());
((IPersistentViewProvider)this).AttachedPersistentView = null;
navService.NavigateTo(new BetterEntranceNavigationTransition());
}
@@ -157,9 +153,7 @@ public abstract partial class InferenceTabViewModelBase
if (result == ContentDialogResult.Primary && textFields[0].Text is { } json)
{
- LoadViewState(
- new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } }
- );
+ LoadViewState(new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } });
}
}
@@ -226,9 +220,7 @@ public abstract partial class InferenceTabViewModelBase
if (this is IParametersLoadableState paramsLoadableVm)
{
- Dispatcher.UIThread.Invoke(
- () => paramsLoadableVm.LoadStateFromParameters(parameters)
- );
+ Dispatcher.UIThread.Invoke(() => paramsLoadableVm.LoadStateFromParameters(parameters));
}
else
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
index b06e9a68..ed787354 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
@@ -11,6 +11,7 @@ using NLog;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
+using StabilityMatrix.Core.Models.Inference;
namespace StabilityMatrix.Avalonia.ViewModels.Base;
@@ -20,12 +21,14 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base;
[JsonDerivedType(typeof(UpscalerCardViewModel), UpscalerCardViewModel.ModuleKey)]
[JsonDerivedType(typeof(ControlNetCardViewModel), ControlNetCardViewModel.ModuleKey)]
[JsonDerivedType(typeof(PromptExpansionCardViewModel), PromptExpansionCardViewModel.ModuleKey)]
+[JsonDerivedType(typeof(LayerDiffuseCardViewModel), LayerDiffuseCardViewModel.ModuleKey)]
[JsonDerivedType(typeof(FreeUModule))]
[JsonDerivedType(typeof(HiresFixModule))]
[JsonDerivedType(typeof(UpscalerModule))]
[JsonDerivedType(typeof(ControlNetModule))]
[JsonDerivedType(typeof(SaveImageModule))]
[JsonDerivedType(typeof(PromptExpansionModule))]
+[JsonDerivedType(typeof(LayerDiffuseModule))]
public abstract class LoadableViewModelBase : ViewModelBase, IJsonLoadableState
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
index 048a6c3d..7edf9920 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
@@ -19,6 +20,7 @@ using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Database;
@@ -157,7 +159,9 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
// Try to find a valid image
var image = images
- ?.Where(img => LocalModelFile.SupportedImageExtensions.Any(img.Url.Contains))
+ ?.Where(
+ img => LocalModelFile.SupportedImageExtensions.Any(img.Url.Contains) && img.Type == "image"
+ )
.FirstOrDefault(image => nsfwEnabled || image.NsfwLevel <= 1);
if (image != null)
{
@@ -216,7 +220,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
MaxDialogHeight = 950,
};
- var prunedDescription = PruneDescription(model);
+ var prunedDescription = Utilities.RemoveHtml(model.Description);
var viewModel = dialogFactory.Get();
viewModel.Dialog = dialog;
@@ -255,7 +259,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
{
var subFolder =
viewModel?.SelectedInstallLocation
- ?? Path.Combine("Models", model.Type.ConvertTo().GetStringValue());
+ ?? Path.Combine(@"Models", model.Type.ConvertTo().GetStringValue());
downloadPath = Path.Combine(settingsManager.LibraryDir, subFolder);
}
@@ -263,23 +267,6 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
await DoImport(model, downloadPath, selectedVersion, selectedFile);
}
- private static string PruneDescription(CivitModel model)
- {
- var prunedDescription =
- model
- .Description?.Replace(" ", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace(" ", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace("
", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
- .Replace("", $"{Environment.NewLine}{Environment.NewLine}") ?? string.Empty;
- prunedDescription = HtmlRegex().Replace(prunedDescription, string.Empty);
- return prunedDescription;
- }
-
private static async Task SaveCmInfo(
CivitModel model,
CivitModelVersion modelVersion,
@@ -310,7 +297,10 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
return null;
}
- var image = modelVersion.Images[0];
+ var image = modelVersion.Images.FirstOrDefault(x => x.Type == "image");
+ if (image is null)
+ return null;
+
var imageExtension = Path.GetExtension(image.Url).TrimStart('.');
if (imageExtension is "jpg" or "jpeg" or "png")
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
index 4754397d..ebf5401a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
@@ -196,10 +196,40 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro
{
var timer = Stopwatch.StartNew();
var queryText = request.Query;
+ var models = new List();
+ CivitModelsResponse? modelsResponse = null;
try
{
- var modelsResponse = await civitApi.GetModels(request);
- var models = modelsResponse.Items;
+ if (!string.IsNullOrWhiteSpace(request.CommaSeparatedModelIds))
+ {
+ // count IDs
+ var ids = request.CommaSeparatedModelIds.Split(',');
+ if (ids.Length > 100)
+ {
+ var idChunks = ids.Chunk(100);
+ foreach (var chunk in idChunks)
+ {
+ request.CommaSeparatedModelIds = string.Join(",", chunk);
+ var chunkModelsResponse = await civitApi.GetModels(request);
+
+ if (chunkModelsResponse.Items != null)
+ {
+ models.AddRange(chunkModelsResponse.Items);
+ }
+ }
+ }
+ else
+ {
+ modelsResponse = await civitApi.GetModels(request);
+ models = modelsResponse.Items;
+ }
+ }
+ else
+ {
+ modelsResponse = await civitApi.GetModels(request);
+ models = modelsResponse.Items;
+ }
+
if (models is null)
{
Logger.Debug(
@@ -241,13 +271,13 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro
InsertedAt = DateTimeOffset.UtcNow,
Request = request,
Items = models,
- Metadata = modelsResponse.Metadata
+ Metadata = modelsResponse?.Metadata
}
);
UpdateModelCards(models, isInfiniteScroll);
- NextPageCursor = modelsResponse.Metadata?.NextCursor;
+ NextPageCursor = modelsResponse?.Metadata?.NextCursor;
}
catch (OperationCanceledException)
{
@@ -428,12 +458,14 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro
{
var connectedModels = CheckpointFile
.GetAllCheckpointFiles(settingsManager.ModelsDirectory)
- .Where(c => c.IsConnectedModel);
+ .Where(c => c.IsConnectedModel)
+ .ToList();
modelRequest.CommaSeparatedModelIds = string.Join(
",",
connectedModels.Select(c => c.ConnectedModel!.ModelId).GroupBy(m => m).Select(g => g.First())
);
+
modelRequest.Sort = null;
modelRequest.Period = null;
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
index fd2ccc1f..743d242e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
@@ -6,6 +6,7 @@ using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
using StabilityMatrix.Avalonia.Views;
@@ -20,7 +21,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[Singleton]
public partial class CheckpointBrowserViewModel : PageViewModelBase
{
- public override string Title => "Model Browser";
+ public override string Title => Resources.Label_ModelBrowser;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.BrainCircuit, IsFilled = true };
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
index a2e2b72a..baebb0fd 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
@@ -137,6 +137,7 @@ public partial class CheckpointFile : ViewModelBase
{
await Task.Run(() => File.Delete(cmInfoPath));
}
+
var settingsManager = App.Services.GetRequiredService();
settingsManager.Transaction(s =>
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs
index e11cccdc..39408129 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs
@@ -500,7 +500,7 @@ public partial class CheckpointFolder : ViewModelBase
await modelInfo.SaveJsonToDirectory(DirectoryPath, modelFileName);
// If available, save thumbnail
- var image = version.Images?.FirstOrDefault();
+ var image = version.Images?.FirstOrDefault(x => x.Type == "image");
if (image != null)
{
var imageExt = Path.GetExtension(image.Url).TrimStart('.');
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
index f5bc68b9..a3720751 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
@@ -25,6 +25,7 @@ using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
+using Resources = StabilityMatrix.Avalonia.Languages.Resources;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;
using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;
@@ -43,7 +44,7 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
private readonly INotificationService notificationService;
private readonly IMetadataImportService metadataImportService;
- public override string Title => "Checkpoints";
+ public override string Title => Resources.Label_Checkpoints;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Notebook, IsFilled = true };
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
index fafd1206..4f271517 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
+using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -30,6 +31,7 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
private readonly ISettingsManager settingsManager;
private readonly INavigationService navigationService;
private readonly IPackageFactory packageFactory;
+ private readonly RunningPackageService runningPackageService;
[ObservableProperty]
private string title = "Hello";
@@ -58,12 +60,14 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
public InferenceConnectionHelpViewModel(
ISettingsManager settingsManager,
INavigationService navigationService,
- IPackageFactory packageFactory
+ IPackageFactory packageFactory,
+ RunningPackageService runningPackageService
)
{
this.settingsManager = settingsManager;
this.navigationService = navigationService;
this.packageFactory = packageFactory;
+ this.runningPackageService = runningPackageService;
// Get comfy type installed packages
var comfyPackages = this.settingsManager.Settings.InstalledPackages.Where(
@@ -122,14 +126,11 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
/// Request launch of the selected package
///
[RelayCommand]
- private void LaunchSelectedPackage()
+ private async Task LaunchSelectedPackage()
{
- if (SelectedPackage?.Id is { } id)
+ if (SelectedPackage is not null)
{
- Dispatcher.UIThread.Post(() =>
- {
- EventManager.Instance.OnPackageLaunchRequested(id);
- });
+ await runningPackageService.StartPackage(SelectedPackage);
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
index 6cee93b6..425b20e7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
@@ -12,6 +12,7 @@ using DynamicData;
using DynamicData.Binding;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Extensions;
+using StabilityMatrix.Avalonia.Models.PackageSteps;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -75,7 +76,7 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
.Connect()
.DeferUntilLoaded()
.Filter(incompatiblePredicate)
- .Filter(p => p.OfferInOneClickInstaller || ShowIncompatiblePackages)
+ .Filter(p => p.OfferInOneClickInstaller)
.Sort(
SortExpressionComparer
.Ascending(p => p.InstallerSortOrder)
@@ -107,18 +108,19 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
{
Task.Run(async () =>
{
+ var installLocation = Path.Combine(
+ settingsManager.LibraryDir,
+ "Packages",
+ selectedPackage.Name
+ );
+
var steps = new List
{
new SetPackageInstallingStep(settingsManager, selectedPackage.Name),
- new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage)
+ new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage),
};
// get latest version & download & install
- var installLocation = Path.Combine(
- settingsManager.LibraryDir,
- "Packages",
- selectedPackage.Name
- );
if (Directory.Exists(installLocation))
{
var installPath = new DirectoryPath(installLocation);
@@ -148,6 +150,11 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
);
steps.Add(downloadStep);
+ var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(
+ Path.Combine(installLocation, "venv")
+ );
+ steps.Add(unpackSiteCustomizeStep);
+
var installStep = new InstallPackageStep(
selectedPackage,
torchVersion,
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
index 2f921bff..09a30cf1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
@@ -196,7 +196,7 @@ public partial class OneClickInstallViewModel : ContentDialogViewModelBase
{
ShowDialogOnStart = true,
HideCloseButton = true,
- ModificationCompleteMessage = $"{SelectedPackage.DisplayName} installed successfully",
+ ModificationCompleteMessage = $"{SelectedPackage.DisplayName} installed successfully"
};
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs
new file mode 100644
index 00000000..d337b52c
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs
@@ -0,0 +1,185 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using AsyncAwaitBestPractices;
+using Avalonia.Controls;
+using CommunityToolkit.Mvvm.ComponentModel;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.Views.Dialogs;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Helper.Factory;
+using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api.OpenArt;
+using StabilityMatrix.Core.Models.Packages.Extensions;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
+
+[View(typeof(OpenArtWorkflowDialog))]
+[ManagedService]
+[Transient]
+public partial class OpenArtWorkflowViewModel(
+ ISettingsManager settingsManager,
+ IPackageFactory packageFactory
+) : ContentDialogViewModelBase
+{
+ public required OpenArtSearchResult Workflow { get; init; }
+
+ [ObservableProperty]
+ private ObservableCollection customNodes = [];
+
+ [ObservableProperty]
+ private string prunedDescription = string.Empty;
+
+ [ObservableProperty]
+ private bool installRequiredNodes = true;
+
+ [ObservableProperty]
+ private InstalledPackage? selectedPackage;
+
+ public PackagePair? SelectedPackagePair =>
+ SelectedPackage is { } package ? packageFactory.GetPackagePair(package) : null;
+
+ public List AvailablePackages =>
+ settingsManager
+ .Settings.InstalledPackages.Where(package => package.PackageName == "ComfyUI")
+ .ToList();
+
+ public List MissingNodes { get; } = [];
+
+ public override async Task OnLoadedAsync()
+ {
+ if (Design.IsDesignMode)
+ return;
+
+ if (settingsManager.Settings.PreferredWorkflowPackage is { } preferredPackage)
+ {
+ SelectedPackage = preferredPackage;
+ }
+ else
+ {
+ SelectedPackage = AvailablePackages.FirstOrDefault();
+ }
+
+ if (SelectedPackage == null)
+ {
+ InstallRequiredNodes = false;
+ }
+
+ CustomNodes = new ObservableCollection(
+ await ParseNodes(Workflow.NodesIndex.ToList())
+ );
+ PrunedDescription = Utilities.RemoveHtml(Workflow.Description);
+ }
+
+ partial void OnSelectedPackageChanged(InstalledPackage? oldValue, InstalledPackage? newValue)
+ {
+ if (oldValue is null)
+ return;
+
+ settingsManager.Transaction(settings =>
+ {
+ settings.PreferredWorkflowPackage = newValue;
+ });
+
+ OnLoadedAsync().SafeFireAndForget();
+ }
+
+ [Localizable(false)]
+ private async Task> ParseNodes(List nodes)
+ {
+ var indexOfFirstDot = nodes.IndexOf(".");
+ if (indexOfFirstDot != -1)
+ {
+ nodes = nodes[(indexOfFirstDot + 1)..];
+ }
+
+ var installedNodesNames = new HashSet();
+ var nameToManifestNodes = new Dictionary();
+
+ var packagePair = SelectedPackagePair;
+
+ if (packagePair?.BasePackage.ExtensionManager is { } extensionManager)
+ {
+ var installedNodes = (
+ await extensionManager.GetInstalledExtensionsLiteAsync(packagePair.InstalledPackage)
+ ).ToList();
+
+ var manifestExtensionsMap = await extensionManager.GetManifestExtensionsMapAsync(
+ extensionManager.GetManifests(packagePair.InstalledPackage)
+ );
+
+ // Add manifestExtensions definition to installedNodes if matching git repository url
+ installedNodes = installedNodes
+ .Select(installedNode =>
+ {
+ if (
+ installedNode.GitRepositoryUrl is not null
+ && manifestExtensionsMap.TryGetValue(
+ installedNode.GitRepositoryUrl,
+ out var manifestExtension
+ )
+ )
+ {
+ installedNode = installedNode with { Definition = manifestExtension };
+ }
+
+ return installedNode;
+ })
+ .ToList();
+
+ // There may be duplicate titles, deduplicate by using the first one
+ nameToManifestNodes = manifestExtensionsMap
+ .GroupBy(x => x.Value.Title)
+ .ToDictionary(x => x.Key, x => x.First().Value);
+
+ installedNodesNames = installedNodes.Select(x => x.Title).ToHashSet();
+ }
+
+ var sections = new List();
+ OpenArtCustomNode? currentSection = null;
+
+ foreach (var node in nodes)
+ {
+ if (node is "." or ",")
+ {
+ currentSection = null; // End of the current section
+ continue;
+ }
+
+ if (currentSection == null)
+ {
+ currentSection = new OpenArtCustomNode
+ {
+ Title = node,
+ IsInstalled = installedNodesNames.Contains(node)
+ };
+
+ // Add missing nodes to the list
+ if (
+ !currentSection.IsInstalled && nameToManifestNodes.TryGetValue(node, out var manifestNode)
+ )
+ {
+ MissingNodes.Add(manifestNode);
+ }
+
+ sections.Add(currentSection);
+ }
+ else
+ {
+ currentSection.Children.Add(node);
+ }
+ }
+
+ if (sections.FirstOrDefault(x => x.Title == "ComfyUI") != null)
+ {
+ sections = sections.Where(x => x.Title != "ComfyUI").ToList();
+ }
+
+ return sections;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelItemViewModel.cs
index 3de1d5da..501fa5db 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelItemViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelItemViewModel.cs
@@ -22,9 +22,9 @@ public partial class RecommendedModelItemViewModel : ViewModelBase
private CivitModel civitModel;
public Uri ThumbnailUrl =>
- ModelVersion.Images?.FirstOrDefault()?.Url == null
+ ModelVersion.Images?.FirstOrDefault(x => x.Type == "image")?.Url == null
? Assets.NoImage
- : new Uri(ModelVersion.Images.First().Url);
+ : new Uri(ModelVersion.Images.First(x => x.Type == "image").Url);
[RelayCommand]
public void ToggleSelection() => IsSelected = !IsSelected;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
index e037408e..5eeaeb6a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
@@ -75,7 +75,7 @@ public partial class RecommendedModelsViewModel : ContentDialogViewModelBase
CivitModels
.Connect()
.DeferUntilLoaded()
- .Filter(f => f.ModelVersion.BaseModel == "SDXL 1.0")
+ .Filter(f => f.ModelVersion.BaseModel == "SDXL 1.0" || f.ModelVersion.BaseModel == "Pony")
.Bind(SdxlModels)
.Subscribe();
}
@@ -206,7 +206,10 @@ public partial class RecommendedModelsViewModel : ContentDialogViewModelBase
return null;
}
- var image = modelVersion.Images[0];
+ var image = modelVersion.Images.FirstOrDefault(x => x.Type == "image");
+ if (image is null)
+ return null;
+
var imageExtension = Path.GetExtension(image.Url).TrimStart('.');
if (imageExtension is "jpg" or "jpeg" or "png")
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
index 2cf7f264..8327e996 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
@@ -99,7 +99,7 @@ public partial class SelectModelVersionViewModel(
var allImages = value
?.ModelVersion
?.Images
- ?.Where(img => nsfwEnabled || img.NsfwLevel <= 1)
+ ?.Where(img => img.Type == "image" && (nsfwEnabled || img.NsfwLevel <= 1))
?.Select(x => new ImageSource(x.Url))
.ToList();
@@ -316,7 +316,9 @@ public partial class SelectModelVersionViewModel(
installLocations.Add(downloadDirectory.ToString().Replace(rootModelsDirectory, "Models"));
- foreach (var directory in downloadDirectory.EnumerateDirectories())
+ foreach (
+ var directory in downloadDirectory.EnumerateDirectories(searchOption: SearchOption.AllDirectories)
+ )
{
installLocations.Add(directory.ToString().Replace(rootModelsDirectory, "Models"));
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
index 46815f62..080c29fa 100644
--- a/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using CommunityToolkit.Mvvm.ComponentModel;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Styles;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
@@ -27,11 +28,9 @@ public partial class FirstLaunchSetupViewModel : ViewModelBase
private RefreshBadgeViewModel checkHardwareBadge =
new()
{
- WorkingToolTipText = "We're checking some hardware specifications to determine compatibility.",
- SuccessToolTipText = "Everything looks good!",
- FailToolTipText =
- "We recommend a GPU with CUDA support for the best experience. "
- + "You can continue without one, but some packages may not work, and inference may be slower.",
+ WorkingToolTipText = Resources.Label_CheckingHardware,
+ SuccessToolTipText = Resources.Label_EverythingLooksGood,
+ FailToolTipText = Resources.Label_NvidiaGpuRecommended,
FailColorBrush = ThemeColors.ThemeYellow,
};
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs
index 496e90fc..65bab36e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs
@@ -1,19 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
+using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Core.Attributes;
-using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
-using StabilityMatrix.Core.Models.FileInterfaces;
-using StabilityMatrix.Core.Services;
+using StabilityMatrix.Core.Models.Api.Comfy;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
@@ -32,7 +33,17 @@ public partial class ControlNetCardViewModel : LoadableViewModelBase
[ObservableProperty]
[Required]
- private HybridModelFile? selectedPreprocessor;
+ private ComfyAuxPreprocessor? selectedPreprocessor;
+
+ [ObservableProperty]
+ [Required]
+ [Range(0, 2048)]
+ private int width;
+
+ [ObservableProperty]
+ [Required]
+ [Range(0, 2048)]
+ private int height;
[ObservableProperty]
[Required]
@@ -62,6 +73,18 @@ public partial class ControlNetCardViewModel : LoadableViewModelBase
ClientManager = clientManager;
SelectImageCardViewModel = vmFactory.Get();
+
+ // Update our width and height when the image changes
+ SelectImageCardViewModel
+ .WhenPropertyChanged(card => card.CurrentBitmapSize)
+ .Subscribe(propertyValue =>
+ {
+ if (!propertyValue.Value.IsEmpty)
+ {
+ Width = propertyValue.Value.Width;
+ Height = propertyValue.Value.Height;
+ }
+ });
}
[RelayCommand]
@@ -79,4 +102,57 @@ public partial class ControlNetCardViewModel : LoadableViewModelBase
confirmDialog.StartDownload();
}
}
+
+ [RelayCommand]
+ private async Task PreviewPreprocessor(ComfyAuxPreprocessor? preprocessor)
+ {
+ if (
+ preprocessor is null
+ || SelectImageCardViewModel.ImageSource is not { } imageSource
+ || SelectImageCardViewModel.IsImageFileNotFound
+ )
+ return;
+
+ var args = new InferenceQueueCustomPromptEventArgs();
+
+ var images = SelectImageCardViewModel.GetInputImages();
+
+ await ClientManager.UploadInputImageAsync(imageSource);
+
+ var image = args.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LoadImage
+ {
+ Name = args.Nodes.GetUniqueName("Preprocessor_LoadImage"),
+ Image =
+ SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
+ ?? throw new ValidationException("No ImageSource")
+ }
+ ).Output1;
+
+ var aioPreprocessor = args.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.AIOPreprocessor
+ {
+ Name = args.Nodes.GetUniqueName("Preprocessor"),
+ Image = image,
+ Preprocessor = preprocessor.ToString(),
+ Resolution = Width is <= 2048 and > 0 ? Width : 512
+ }
+ );
+
+ args.Builder.Connections.OutputNodes.Add(
+ args.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.PreviewImage
+ {
+ Name = args.Nodes.GetUniqueName("Preprocessor_OutputImage"),
+ Images = aioPreprocessor.Output
+ }
+ )
+ );
+
+ // Queue
+ Dispatcher.UIThread.Post(() => EventManager.Instance.OnInferenceQueueCustomPrompt(args));
+
+ // We don't know when it's done so wait a bit?
+ await Task.Delay(1000);
+ }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs
index 50c4734f..7600384a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs
@@ -27,9 +27,17 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel
IInferenceClientManager inferenceClientManager,
INotificationService notificationService,
ISettingsManager settingsManager,
- IModelIndexService modelIndexService
+ IModelIndexService modelIndexService,
+ RunningPackageService runningPackageService
)
- : base(notificationService, inferenceClientManager, settingsManager, vmFactory, modelIndexService)
+ : base(
+ notificationService,
+ inferenceClientManager,
+ settingsManager,
+ vmFactory,
+ modelIndexService,
+ runningPackageService
+ )
{
SelectImageCardViewModel = vmFactory.Get();
@@ -48,26 +56,30 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel
_ => Convert.ToUInt64(SeedCardViewModel.Seed)
};
- BatchSizeCardViewModel.ApplyStep(args);
+ var applyArgs = args.ToModuleApplyStepEventArgs();
+
+ BatchSizeCardViewModel.ApplyStep(applyArgs);
// Load models
- ModelCardViewModel.ApplyStep(args);
+ ModelCardViewModel.ApplyStep(applyArgs);
// Setup image latent source
- SelectImageCardViewModel.ApplyStep(args);
+ SelectImageCardViewModel.ApplyStep(applyArgs);
// Prompts and loras
- PromptCardViewModel.ApplyStep(args);
+ PromptCardViewModel.ApplyStep(applyArgs);
// Setup Sampler and Refiner if enabled
- SamplerCardViewModel.ApplyStep(args);
+ SamplerCardViewModel.ApplyStep(applyArgs);
// Apply module steps
foreach (var module in ModulesCardViewModel.Cards.OfType())
{
- module.ApplyStep(args);
+ module.ApplyStep(applyArgs);
}
+ applyArgs.InvokeAllPreOutputActions();
+
builder.SetupOutputImage();
}
@@ -77,12 +89,12 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel
var mainImages = SelectImageCardViewModel.GetInputImages();
var samplerImages = SamplerCardViewModel
- .ModulesCardViewModel
- .Cards
- .OfType()
+ .ModulesCardViewModel.Cards.OfType()
.SelectMany(m => m.GetInputImages());
- var moduleImages = ModulesCardViewModel.Cards.OfType().SelectMany(m => m.GetInputImages());
+ var moduleImages = ModulesCardViewModel
+ .Cards.OfType()
+ .SelectMany(m => m.GetInputImages());
return mainImages.Concat(samplerImages).Concat(moduleImages);
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
index b57e3406..035774c1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -65,9 +65,10 @@ public partial class InferenceImageToVideoViewModel
IInferenceClientManager inferenceClientManager,
ISettingsManager settingsManager,
ServiceManager vmFactory,
- IModelIndexService modelIndexService
+ IModelIndexService modelIndexService,
+ RunningPackageService runningPackageService
)
- : base(vmFactory, inferenceClientManager, notificationService, settingsManager)
+ : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)
{
this.notificationService = notificationService;
this.modelIndexService = modelIndexService;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
index 2ca1dbd8..540de0d9 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
@@ -59,9 +59,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase
INotificationService notificationService,
IInferenceClientManager inferenceClientManager,
ISettingsManager settingsManager,
- ServiceManager vmFactory
+ ServiceManager vmFactory,
+ RunningPackageService runningPackageService
)
- : base(vmFactory, inferenceClientManager, notificationService, settingsManager)
+ : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)
{
this.notificationService = notificationService;
@@ -142,7 +143,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase
}
///
- protected override async Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)
+ protected override async Task GenerateImageImpl(
+ GenerateOverrides overrides,
+ CancellationToken cancellationToken
+ )
{
if (!ClientManager.IsConnected)
{
@@ -169,7 +173,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase
Client = ClientManager.Client,
Nodes = buildPromptArgs.Builder.ToNodeDictionary(),
OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),
- Parameters = new GenerationParameters { ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name, },
+ Parameters = new GenerationParameters
+ {
+ ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name,
+ },
Project = InferenceProjectDocument.FromLoadable(this)
};
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
index 12d0d5fc..04e46f05 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
@@ -59,9 +59,10 @@ public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, I
IInferenceClientManager inferenceClientManager,
ISettingsManager settingsManager,
ServiceManager vmFactory,
- IModelIndexService modelIndexService
+ IModelIndexService modelIndexService,
+ RunningPackageService runningPackageService
)
- : base(vmFactory, inferenceClientManager, notificationService, settingsManager)
+ : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)
{
this.notificationService = notificationService;
this.modelIndexService = modelIndexService;
@@ -130,10 +131,12 @@ public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, I
_ => Convert.ToUInt64(SeedCardViewModel.Seed)
};
- BatchSizeCardViewModel.ApplyStep(args);
+ var applyArgs = args.ToModuleApplyStepEventArgs();
+
+ BatchSizeCardViewModel.ApplyStep(applyArgs);
// Load models
- ModelCardViewModel.ApplyStep(args);
+ ModelCardViewModel.ApplyStep(applyArgs);
// Setup empty latent
builder.SetupEmptyLatentSource(
@@ -144,17 +147,19 @@ public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, I
);
// Prompts and loras
- PromptCardViewModel.ApplyStep(args);
+ PromptCardViewModel.ApplyStep(applyArgs);
// Setup Sampler and Refiner if enabled
- SamplerCardViewModel.ApplyStep(args);
+ SamplerCardViewModel.ApplyStep(applyArgs);
// Hires fix if enabled
foreach (var module in ModulesCardViewModel.Cards.OfType())
{
- module.ApplyStep(args);
+ module.ApplyStep(applyArgs);
}
+ applyArgs.InvokeAllPreOutputActions();
+
builder.SetupOutputImage();
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs
new file mode 100644
index 00000000..865a4c74
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using CommunityToolkit.Mvvm.ComponentModel;
+using KGySoft.CoreLibraries;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Models.Inference;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference;
+
+[Transient]
+[ManagedService]
+[View(typeof(LayerDiffuseCard))]
+public partial class LayerDiffuseCardViewModel : LoadableViewModelBase, IComfyStep
+{
+ public const string ModuleKey = "LayerDiffuse";
+
+ [ObservableProperty]
+ private LayerDiffuseMode selectedMode = LayerDiffuseMode.None;
+
+ public IEnumerable AvailableModes => Enum.GetValues();
+
+ [ObservableProperty]
+ [NotifyDataErrorInfo]
+ [Required]
+ [Range(-1d, 3d)]
+ private double weight = 1;
+
+ ///
+ public void ApplyStep(ModuleApplyStepEventArgs e)
+ {
+ if (SelectedMode == LayerDiffuseMode.None)
+ return;
+
+ var sdType = SelectedMode switch
+ {
+ LayerDiffuseMode.GenerateForegroundWithTransparencySD15 => "SD15",
+ LayerDiffuseMode.GenerateForegroundWithTransparencySDXL => "SDXL",
+ LayerDiffuseMode.None => throw new ArgumentOutOfRangeException(),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ // Choose config based on mode
+ var config = SelectedMode switch
+ {
+ LayerDiffuseMode.GenerateForegroundWithTransparencySD15
+ => "SD15, Attention Injection, attn_sharing",
+ LayerDiffuseMode.GenerateForegroundWithTransparencySDXL => "SDXL, Conv Injection",
+ LayerDiffuseMode.None => throw new ArgumentOutOfRangeException(),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ foreach (var modelConnections in e.Temp.Models.Values)
+ {
+ var layerDiffuseApply = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LayeredDiffusionApply
+ {
+ Name = e.Nodes.GetUniqueName($"LayerDiffuseApply_{modelConnections.Name}"),
+ Model = modelConnections.Model,
+ Config = config,
+ Weight = Weight,
+ }
+ );
+
+ modelConnections.Model = layerDiffuseApply.Output;
+ }
+
+ // Add pre output action
+ e.PreOutputActions.Add(applyArgs =>
+ {
+ // Use last latent for decode
+ var latent =
+ applyArgs.Builder.Connections.LastPrimaryLatent
+ ?? throw new InvalidOperationException("Connections.LastPrimaryLatent not set");
+
+ // Convert primary to image if not already
+ var primaryImage = applyArgs.Builder.GetPrimaryAsImage();
+ applyArgs.Builder.Connections.Primary = primaryImage;
+
+ // Add a Layer Diffuse Decode
+ var decode = applyArgs.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LayeredDiffusionDecodeRgba
+ {
+ Name = applyArgs.Nodes.GetUniqueName("LayerDiffuseDecode"),
+ Samples = latent,
+ Images = primaryImage,
+ SdVersion = sdType
+ }
+ );
+
+ // Set primary to decode output
+ applyArgs.Builder.Connections.Primary = decode.Output;
+ });
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
index 23260daf..77955192 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
@@ -6,6 +6,9 @@ using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Models.Api.Comfy;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
@@ -39,7 +42,7 @@ public class ControlNetModule : ModuleBase
{
var card = GetCard();
- var imageLoad = e.Nodes.AddTypedNode(
+ var image = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.LoadImage
{
Name = e.Nodes.GetUniqueName("ControlNet_LoadImage"),
@@ -47,7 +50,101 @@ public class ControlNetModule : ModuleBase
card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
?? throw new ValidationException("No ImageSource")
}
- );
+ ).Output1;
+
+ if (card.SelectedPreprocessor is { } preprocessor && preprocessor != ComfyAuxPreprocessor.None)
+ {
+ var aioPreprocessor = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.AIOPreprocessor
+ {
+ Name = e.Nodes.GetUniqueName("ControlNet_Preprocessor"),
+ Image = image,
+ Preprocessor = preprocessor.ToString(),
+ // Use width if valid, else default of 512
+ Resolution = card.Width is <= 2048 and > 0 ? card.Width : 512
+ }
+ );
+
+ image = aioPreprocessor.Output;
+ }
+
+ // If ReferenceOnly is selected, use special node
+ if (card.SelectedModel == RemoteModels.ControlNetReferenceOnlyModel)
+ {
+ // We need to rescale image to be the current primary size if it's not already
+ var originalPrimary = e.Temp.Primary!.Unwrap();
+ var originalPrimarySize = e.Builder.Connections.PrimarySize;
+
+ if (card.SelectImageCardViewModel.CurrentBitmapSize != originalPrimarySize)
+ {
+ var scaled = e.Builder.Group_Upscale(
+ e.Nodes.GetUniqueName("ControlNet_Rescale"),
+ image,
+ e.Temp.GetDefaultVAE(),
+ ComfyUpscaler.NearestExact,
+ originalPrimarySize.Width,
+ originalPrimarySize.Height
+ );
+ e.Temp.Primary = scaled;
+ }
+ else
+ {
+ e.Temp.Primary = image;
+ }
+
+ // Set image as new latent source, add reference only node
+ var model = e.Temp.GetRefinerOrBaseModel();
+ var controlNetReferenceOnly = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.ReferenceOnlySimple
+ {
+ Name = e.Nodes.GetUniqueName("ControlNet_ReferenceOnly"),
+ Reference = e.Builder.GetPrimaryAsLatent(
+ e.Temp.Primary,
+ e.Builder.Connections.GetDefaultVAE()
+ ),
+ Model = model
+ }
+ );
+
+ var referenceOnlyModel = controlNetReferenceOnly.Output1;
+
+ // If ControlNet strength is not 1, add Model Merge
+ if (Math.Abs(card.Strength - 1) > 0.01)
+ {
+ var modelBlend = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.ModelMergeSimple
+ {
+ Name = e.Nodes.GetUniqueName("ControlNet_ReferenceOnly_ModelMerge"),
+ Model1 = referenceOnlyModel,
+ Model2 = e.Temp.GetRefinerOrBaseModel(),
+ // Where 0 is full reference only, 1 is full original
+ Ratio = 1 - card.Strength
+ }
+ );
+
+ referenceOnlyModel = modelBlend.Output;
+ }
+
+ // Set output as new primary and model source
+ if (model == e.Temp.Refiner.Model)
+ {
+ e.Temp.Refiner.Model = referenceOnlyModel;
+ }
+ else
+ {
+ e.Temp.Base.Model = referenceOnlyModel;
+ }
+ e.Temp.Primary = controlNetReferenceOnly.Output2;
+
+ // Indicate that the Primary latent has been temp batched
+ // https://github.com/comfyanonymous/ComfyUI_experiments/issues/11
+
+ e.Temp.IsPrimaryTempBatched = true;
+ // Index 0 is the original image, index 1 is the reference only latent
+ e.Temp.PrimaryTempBatchPickIndex = 1;
+
+ return;
+ }
var controlNetLoader = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.ControlNetLoader
@@ -62,36 +159,36 @@ public class ControlNetModule : ModuleBase
new ComfyNodeBuilder.ControlNetApplyAdvanced
{
Name = e.Nodes.GetUniqueName("ControlNetApply"),
- Image = imageLoad.Output1,
+ Image = image,
ControlNet = controlNetLoader.Output,
- Positive = e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"),
- Negative = e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"),
+ Positive = e.Temp.Base.Conditioning!.Unwrap().Positive,
+ Negative = e.Temp.Base.Conditioning.Negative,
Strength = card.Strength,
StartPercent = card.StartPercent,
EndPercent = card.EndPercent,
}
);
- e.Temp.Conditioning = (controlNetApply.Output1, controlNetApply.Output2);
+ e.Temp.Base.Conditioning = (controlNetApply.Output1, controlNetApply.Output2);
// Refiner if available
- if (e.Temp.RefinerConditioning is not null)
+ if (e.Temp.Refiner.Conditioning is not null)
{
var controlNetRefinerApply = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.ControlNetApplyAdvanced
{
Name = e.Nodes.GetUniqueName("Refiner_ControlNetApply"),
- Image = imageLoad.Output1,
+ Image = image,
ControlNet = controlNetLoader.Output,
- Positive = e.Temp.RefinerConditioning.Positive,
- Negative = e.Temp.RefinerConditioning.Negative,
+ Positive = e.Temp.Refiner.Conditioning!.Unwrap().Positive,
+ Negative = e.Temp.Refiner.Conditioning.Negative,
Strength = card.Strength,
StartPercent = card.StartPercent,
EndPercent = card.EndPercent,
}
);
- e.Temp.RefinerConditioning = (controlNetRefinerApply.Output1, controlNetRefinerApply.Output2);
+ e.Temp.Refiner.Conditioning = (controlNetRefinerApply.Output1, controlNetRefinerApply.Output2);
}
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
index 588cd263..94a0d1bc 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
@@ -78,30 +78,39 @@ public partial class HiresFixModule : ModuleBase
);
}
- var hiresSampler = builder
- .Nodes
- .AddTypedNode(
- new ComfyNodeBuilder.KSampler
- {
- Name = builder.Nodes.GetUniqueName("HiresFix_Sampler"),
- Model = builder.Connections.GetRefinerOrBaseModel(),
- Seed = builder.Connections.Seed,
- Steps = samplerCard.Steps,
- Cfg = samplerCard.CfgScale,
- SamplerName =
- samplerCard.SelectedSampler?.Name
- ?? e.Builder.Connections.PrimarySampler?.Name
- ?? throw new ArgumentException("No PrimarySampler"),
- Scheduler =
- samplerCard.SelectedScheduler?.Name
- ?? e.Builder.Connections.PrimaryScheduler?.Name
- ?? throw new ArgumentException("No PrimaryScheduler"),
- Positive = builder.Connections.GetRefinerOrBaseConditioning().Positive,
- Negative = builder.Connections.GetRefinerOrBaseConditioning().Negative,
- LatentImage = builder.GetPrimaryAsLatent(),
- Denoise = samplerCard.DenoiseStrength
- }
- );
+ // If we need to inherit primary sampler addons, use their temp args
+ if (samplerCard.InheritPrimarySamplerAddons)
+ {
+ e.Temp = e.Builder.Connections.BaseSamplerTemporaryArgs ?? e.CreateTempFromBuilder();
+ }
+ else
+ {
+ // otherwise just use new ones
+ e.Temp = e.CreateTempFromBuilder();
+ }
+
+ var hiresSampler = builder.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.KSampler
+ {
+ Name = builder.Nodes.GetUniqueName("HiresFix_Sampler"),
+ Model = builder.Connections.GetRefinerOrBaseModel(),
+ Seed = builder.Connections.Seed,
+ Steps = samplerCard.Steps,
+ Cfg = samplerCard.CfgScale,
+ SamplerName =
+ samplerCard.SelectedSampler?.Name
+ ?? e.Builder.Connections.PrimarySampler?.Name
+ ?? throw new ArgumentException("No PrimarySampler"),
+ Scheduler =
+ samplerCard.SelectedScheduler?.Name
+ ?? e.Builder.Connections.PrimaryScheduler?.Name
+ ?? throw new ArgumentException("No PrimaryScheduler"),
+ Positive = e.Temp.GetRefinerOrBaseConditioning().Positive,
+ Negative = e.Temp.GetRefinerOrBaseConditioning().Negative,
+ LatentImage = builder.GetPrimaryAsLatent(),
+ Denoise = samplerCard.DenoiseStrength
+ }
+ );
// Set as primary
builder.Connections.Primary = hiresSampler.Output;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs
new file mode 100644
index 00000000..11a70114
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs
@@ -0,0 +1,26 @@
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.Services;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
+
+[ManagedService]
+[Transient]
+public class LayerDiffuseModule : ModuleBase
+{
+ ///
+ public LayerDiffuseModule(ServiceManager vmFactory)
+ : base(vmFactory)
+ {
+ Title = "Layer Diffuse";
+ AddCards(vmFactory.Get());
+ }
+
+ ///
+ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
+ {
+ var card = GetCard();
+ card.ApplyStep(e);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
index d4a2b895..919e4e30 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
@@ -20,6 +20,7 @@ using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Models.Settings;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
@@ -30,6 +31,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
public partial class PromptCardViewModel : LoadableViewModelBase, IParametersLoadableState, IComfyStep
{
private readonly IModelIndexService modelIndexService;
+ private readonly ISettingsManager settingsManager;
///
/// Cache of prompt text to tokenized Prompt
@@ -48,6 +50,9 @@ public partial class PromptCardViewModel : LoadableViewModelBase, IParametersLoa
[ObservableProperty]
private bool isAutoCompletionEnabled;
+ [ObservableProperty]
+ private bool isHelpButtonTeachingTipOpen;
+
///
public PromptCardViewModel(
ICompletionProvider completionProvider,
@@ -59,6 +64,7 @@ public partial class PromptCardViewModel : LoadableViewModelBase, IParametersLoa
)
{
this.modelIndexService = modelIndexService;
+ this.settingsManager = settingsManager;
CompletionProvider = completionProvider;
TokenizerProvider = tokenizerProvider;
SharedState = sharedState;
@@ -77,6 +83,30 @@ public partial class PromptCardViewModel : LoadableViewModelBase, IParametersLoa
);
}
+ partial void OnIsHelpButtonTeachingTipOpenChanging(bool oldValue, bool newValue)
+ {
+ // If the teaching tip is being closed, save the setting
+ if (oldValue && !newValue)
+ {
+ settingsManager.Transaction(settings =>
+ {
+ settings.SeenTeachingTips.Add(TeachingTip.InferencePromptHelpButtonTip);
+ });
+ }
+ }
+
+ ///
+ public override void OnLoaded()
+ {
+ base.OnLoaded();
+
+ // Show teaching tip for help button if not seen
+ if (!settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.InferencePromptHelpButtonTip))
+ {
+ IsHelpButtonTeachingTipOpen = true;
+ }
+ }
+
///
/// Applies the prompt step.
/// Requires:
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
index c2409259..664ab0c8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
@@ -87,6 +87,11 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
[Required]
private ComfyScheduler? selectedScheduler = ComfyScheduler.Normal;
+ [ObservableProperty]
+ [property: Category("Settings")]
+ [property: DisplayName("Inherit Primary Sampler Addons")]
+ private bool inheritPrimarySamplerAddons = true;
+
[JsonPropertyName("Modules")]
public StackEditableCardViewModel ModulesCardViewModel { get; }
@@ -104,7 +109,12 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
ModulesCardViewModel = vmFactory.Get(modulesCard =>
{
modulesCard.Title = Resources.Label_Addons;
- modulesCard.AvailableModules = [typeof(FreeUModule), typeof(ControlNetModule)];
+ modulesCard.AvailableModules =
+ [
+ typeof(FreeUModule),
+ typeof(ControlNetModule),
+ typeof(LayerDiffuseModule)
+ ];
});
}
@@ -130,8 +140,7 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
}
// Provide temp values
- e.Temp.Conditioning = e.Builder.Connections.Base.Conditioning;
- e.Temp.RefinerConditioning = e.Builder.Connections.Refiner.Conditioning;
+ e.Temp = e.CreateTempFromBuilder();
// Apply steps from our addons
ApplyAddonSteps(e);
@@ -142,17 +151,26 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
if (!e.Nodes.ContainsKey("Sampler"))
{
ApplyStepsInitialSampler(e);
+
+ // Save temp
+ e.Builder.Connections.BaseSamplerTemporaryArgs = e.Temp;
}
else
{
- ApplyStepsAdditionalSampler(e);
+ // Hires does its own sampling so just throw I guess
+ throw new InvalidOperationException(
+ "Sampler ApplyStep was called when Sampler node already exists"
+ );
}
}
private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)
{
// Get primary as latent using vae
- var primaryLatent = e.Builder.GetPrimaryAsLatent();
+ var primaryLatent = e.Builder.GetPrimaryAsLatent(
+ e.Temp.Primary!.Unwrap(),
+ e.Builder.Connections.GetDefaultVAE()
+ );
// Set primary sampler and scheduler
var primarySampler = SelectedSampler ?? throw new ValidationException("Sampler not selected");
@@ -162,8 +180,8 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
e.Builder.Connections.PrimaryScheduler = primaryScheduler;
// Use Temp Conditioning that may be modified by addons
- var conditioning = e.Temp.Conditioning.Unwrap();
- var refinerConditioning = e.Temp.RefinerConditioning;
+ var conditioning = e.Temp.Base.Conditioning.Unwrap();
+ var refinerConditioning = e.Temp.Refiner.Conditioning;
// Use custom sampler if SDTurbo scheduler is selected
if (e.Builder.Connections.PrimaryScheduler == ComfyScheduler.SDTurbo)
@@ -209,21 +227,16 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
);
e.Builder.Connections.Primary = sampler.Output1;
-
- return;
}
-
// Use KSampler if no refiner, otherwise need KSamplerAdvanced
- if (e.Builder.Connections.Refiner.Model is null)
+ else if (e.Builder.Connections.Refiner.Model is null)
{
- var baseConditioning = e.Builder.Connections.Base.Conditioning.Unwrap();
-
// No refiner
var sampler = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.KSampler
{
Name = "Sampler",
- Model = e.Builder.Connections.Base.Model.Unwrap(),
+ Model = e.Temp.Base.Model!.Unwrap(),
Seed = e.Builder.Connections.Seed,
SamplerName = primarySampler.Name,
Scheduler = primaryScheduler.Name,
@@ -245,7 +258,7 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
new ComfyNodeBuilder.KSamplerAdvanced
{
Name = "Sampler",
- Model = e.Builder.Connections.Base.Model.Unwrap(),
+ Model = e.Temp.Base.Model!.Unwrap(),
AddNoise = true,
NoiseSeed = e.Builder.Connections.Seed,
Steps = TotalSteps,
@@ -261,8 +274,30 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
}
);
+ e.Builder.Connections.Primary = sampler.Output;
+ }
+
+ // If temp batched, add a LatentFromBatch to pick the temp batch right after first sampler
+ if (e.Temp.IsPrimaryTempBatched)
+ {
+ e.Builder.Connections.Primary = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.LatentFromBatch
+ {
+ Name = e.Nodes.GetUniqueName("ControlNet_LatentFromBatch"),
+ Samples = e.Builder.GetPrimaryAsLatent(),
+ BatchIndex = e.Temp.PrimaryTempBatchPickIndex,
+ // Use max length here as recommended
+ // https://github.com/comfyanonymous/ComfyUI_experiments/issues/11
+ Length = 64
+ }
+ ).Output;
+ }
+
+ // Refiner
+ if (e.Builder.Connections.Refiner.Model is not null)
+ {
// Add refiner sampler
- var refinerSampler = e.Nodes.AddTypedNode(
+ e.Builder.Connections.Primary = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.KSamplerAdvanced
{
Name = "Sampler_Refiner",
@@ -276,19 +311,15 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
Positive = refinerConditioning!.Positive,
Negative = refinerConditioning.Negative,
// Connect to previous sampler
- LatentImage = sampler.Output,
+ LatentImage = e.Builder.GetPrimaryAsLatent(),
StartAtStep = Steps,
EndAtStep = TotalSteps,
ReturnWithLeftoverNoise = false
}
- );
-
- e.Builder.Connections.Primary = refinerSampler.Output;
+ ).Output;
}
}
- private void ApplyStepsAdditionalSampler(ModuleApplyStepEventArgs e) { }
-
///
/// Applies each step of our addons
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs
index c36fa128..c641f18f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
+using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Text.Json;
@@ -16,6 +18,7 @@ using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using NLog;
using StabilityMatrix.Avalonia.Extensions;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -51,8 +54,10 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
private readonly ServiceManager vmFactory;
private readonly IModelIndexService modelIndexService;
private readonly ILiteDbContext liteDbContext;
+ private readonly RunningPackageService runningPackageService;
+ private Guid? selectedPackageId;
- public override string Title => "Inference";
+ public override string Title => Resources.Label_Inference;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.AppGeneric, IsFilled = true };
@@ -62,7 +67,7 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
State = ProgressState.Failed,
FailToolTipText = "Not connected",
FailIcon = FluentAvalonia.UI.Controls.Symbol.Refresh,
- SuccessToolTipText = "Connected",
+ SuccessToolTipText = Resources.Label_Connected,
};
public IInferenceClientManager ClientManager { get; }
@@ -86,6 +91,8 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
public bool IsComfyRunning => RunningPackage?.BasePackage is ComfyUI;
+ private IDisposable? onStartupComplete;
+
public InferenceViewModel(
ServiceManager vmFactory,
INotificationService notificationService,
@@ -93,6 +100,7 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
ISettingsManager settingsManager,
IModelIndexService modelIndexService,
ILiteDbContext liteDbContext,
+ RunningPackageService runningPackageService,
SharedState sharedState
)
{
@@ -101,12 +109,13 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
this.settingsManager = settingsManager;
this.modelIndexService = modelIndexService;
this.liteDbContext = liteDbContext;
+ this.runningPackageService = runningPackageService;
ClientManager = inferenceClientManager;
SharedState = sharedState;
// Keep RunningPackage updated with the current package pair
- EventManager.Instance.RunningPackageStatusChanged += OnRunningPackageStatusChanged;
+ runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged;
// "Send to Inference"
EventManager.Instance.InferenceTextToImageRequested += OnInferenceTextToImageRequested;
@@ -114,58 +123,112 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
EventManager.Instance.InferenceImageToImageRequested += OnInferenceImageToImageRequested;
EventManager.Instance.InferenceImageToVideoRequested += OnInferenceImageToVideoRequested;
+ // Global requests for custom prompt queueing
+ EventManager.Instance.InferenceQueueCustomPrompt += OnInferenceQueueCustomPromptRequested;
+
MenuSaveAsCommand.WithConditionalNotificationErrorHandler(notificationService);
MenuOpenProjectCommand.WithConditionalNotificationErrorHandler(notificationService);
}
+ private void DisconnectFromComfy()
+ {
+ RunningPackage = null;
+
+ // Cancel any pending connection
+ if (ConnectCancelCommand.CanExecute(null))
+ {
+ ConnectCancelCommand.Execute(null);
+ }
+ onStartupComplete?.Dispose();
+ onStartupComplete = null;
+ IsWaitingForConnection = false;
+
+ // Disconnect
+ Logger.Trace("On package close - disconnecting");
+ DisconnectCommand.Execute(null);
+ }
+
///
/// Updates the RunningPackage property when the running package changes.
/// Also starts a connection to the backend if a new ComfyUI package is running.
/// And disconnects if the package is closed.
///
- private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusChangedEventArgs e)
+ private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
- RunningPackage = e.CurrentPackagePair;
+ if (
+ e.NewItems?.OfType>().Select(x => x.Value)
+ is not { } newItems
+ )
+ {
+ if (RunningPackage != null)
+ {
+ DisconnectFromComfy();
+ }
+ return;
+ }
- IDisposable? onStartupComplete = null;
+ var comfyViewModel = newItems.FirstOrDefault(
+ vm =>
+ vm.RunningPackage.InstalledPackage.Id == selectedPackageId
+ || vm.RunningPackage.BasePackage is ComfyUI
+ );
- Dispatcher.UIThread.Post(() =>
+ if (comfyViewModel is null && RunningPackage?.BasePackage is ComfyUI)
{
- if (e.CurrentPackagePair?.BasePackage is ComfyUI package)
- {
- IsWaitingForConnection = true;
- onStartupComplete = Observable
- .FromEventPattern(package, nameof(package.StartupComplete))
- .Take(1)
- .Subscribe(_ =>
+ DisconnectFromComfy();
+ }
+ else if (comfyViewModel != null && RunningPackage == null)
+ {
+ IsWaitingForConnection = true;
+ RunningPackage = comfyViewModel.RunningPackage;
+ onStartupComplete = Observable
+ .FromEventPattern(
+ comfyViewModel.RunningPackage.BasePackage,
+ nameof(comfyViewModel.RunningPackage.BasePackage.StartupComplete)
+ )
+ .Take(1)
+ .Subscribe(_ =>
+ {
+ Dispatcher.UIThread.Post(() =>
{
- Dispatcher.UIThread.Post(() =>
+ if (ConnectCommand.CanExecute(null))
{
- if (ConnectCommand.CanExecute(null))
- {
- Logger.Trace("On package launch - starting connection");
- ConnectCommand.Execute(null);
- }
- IsWaitingForConnection = false;
- });
+ Logger.Trace("On package launch - starting connection");
+ ConnectCommand.Execute(null);
+ }
+
+ IsWaitingForConnection = false;
});
- }
- else
- {
- // Cancel any pending connection
- if (ConnectCancelCommand.CanExecute(null))
+ });
+ }
+ }
+
+ private void OnInferenceQueueCustomPromptRequested(object? sender, InferenceQueueCustomPromptEventArgs e)
+ {
+ // Get currently selected tab
+ var currentTab = SelectedTab;
+
+ if (currentTab is InferenceGenerationViewModelBase generationViewModel)
+ {
+ Dispatcher
+ .UIThread.InvokeAsync(async () =>
{
- ConnectCancelCommand.Execute(null);
- }
- onStartupComplete?.Dispose();
- onStartupComplete = null;
- IsWaitingForConnection = false;
+ await generationViewModel.RunCustomGeneration(e);
+ })
+ .SafeFireAndForget(ex =>
+ {
+ Logger.Error(ex, "Failed to queue prompt");
- // Disconnect
- Logger.Trace("On package close - disconnecting");
- DisconnectCommand.Execute(null);
- }
- });
+ Dispatcher.UIThread.Post(() =>
+ {
+ notificationService.ShowPersistent(
+ "Failed to queue prompt",
+ $"{ex.GetType().Name}: {ex.Message}",
+ NotificationType.Error
+ );
+ });
+ });
+ }
}
public override void OnLoaded()
@@ -390,7 +453,12 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
private async Task ShowConnectionHelp()
{
var vm = vmFactory.Get();
- await vm.CreateDialog().ShowAsync();
+ var result = await vm.CreateDialog().ShowAsync();
+
+ if (result != ContentDialogResult.Primary)
+ return;
+
+ selectedPackageId = vm.SelectedPackage?.Id;
}
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
new file mode 100644
index 00000000..0bc14b2c
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
@@ -0,0 +1,173 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using AsyncAwaitBestPractices;
+using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using DynamicData;
+using DynamicData.Binding;
+using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.Services;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.Views;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Models.Api.OpenArt;
+using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Avalonia.ViewModels;
+
+[View(typeof(InstalledWorkflowsPage))]
+[Singleton]
+public partial class InstalledWorkflowsViewModel(
+ ISettingsManager settingsManager,
+ INotificationService notificationService
+) : TabViewModelBase, IDisposable
+{
+ public override string Header => Resources.TabLabel_InstalledWorkflows;
+
+ private readonly SourceCache workflowsCache =
+ new(x => x.Workflow?.Id ?? Guid.NewGuid().ToString());
+
+ [ObservableProperty]
+ private IObservableCollection displayedWorkflows =
+ new ObservableCollectionExtended();
+
+ protected override async Task OnInitialLoadedAsync()
+ {
+ await base.OnInitialLoadedAsync();
+
+ workflowsCache.Connect().DeferUntilLoaded().Bind(DisplayedWorkflows).Subscribe();
+
+ if (Design.IsDesignMode)
+ return;
+
+ await LoadInstalledWorkflowsAsync();
+ EventManager.Instance.WorkflowInstalled += OnWorkflowInstalled;
+ }
+
+ [RelayCommand]
+ private async Task LoadInstalledWorkflowsAsync()
+ {
+ workflowsCache.Clear();
+
+ if (!Directory.Exists(settingsManager.WorkflowDirectory))
+ {
+ Directory.CreateDirectory(settingsManager.WorkflowDirectory);
+ }
+
+ foreach (
+ var workflowPath in Directory.EnumerateFiles(
+ settingsManager.WorkflowDirectory,
+ "*.json",
+ SearchOption.AllDirectories
+ )
+ )
+ {
+ try
+ {
+ var json = await File.ReadAllTextAsync(workflowPath);
+ var metadata = JsonSerializer.Deserialize(json);
+
+ if (metadata?.Workflow == null)
+ {
+ metadata = new OpenArtMetadata
+ {
+ Workflow = new OpenArtSearchResult
+ {
+ Id = Guid.NewGuid().ToString(),
+ Name = Path.GetFileNameWithoutExtension(workflowPath)
+ }
+ };
+ }
+
+ metadata.FilePath = [await App.StorageProvider.TryGetFileFromPathAsync(workflowPath)];
+ workflowsCache.AddOrUpdate(metadata);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+ }
+ }
+
+ [RelayCommand]
+ private async Task OpenInExplorer(OpenArtMetadata metadata)
+ {
+ if (metadata.FilePath == null)
+ return;
+
+ var path = metadata.FilePath.FirstOrDefault()?.Path.ToString();
+ if (string.IsNullOrWhiteSpace(path))
+ return;
+
+ await ProcessRunner.OpenFileBrowser(path);
+ }
+
+ [RelayCommand]
+ private void OpenOnOpenArt(OpenArtMetadata metadata)
+ {
+ if (metadata.Workflow == null)
+ return;
+
+ ProcessRunner.OpenUrl($"https://openart.ai/workflows/{metadata.Workflow.Id}");
+ }
+
+ [RelayCommand]
+ private async Task DeleteAsync(OpenArtMetadata metadata)
+ {
+ var confirmationDialog = new BetterContentDialog
+ {
+ Title = Resources.Label_AreYouSure,
+ Content = Resources.Label_ActionCannotBeUndone,
+ PrimaryButtonText = Resources.Action_Delete,
+ SecondaryButtonText = Resources.Action_Cancel,
+ DefaultButton = ContentDialogButton.Primary,
+ IsSecondaryButtonEnabled = true,
+ };
+ var dialogResult = await confirmationDialog.ShowAsync();
+ if (dialogResult != ContentDialogResult.Primary)
+ return;
+
+ await using var delay = new MinimumDelay(200, 500);
+
+ var path = metadata?.FilePath?.FirstOrDefault()?.Path.ToString().Replace("file:///", "");
+ if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
+ {
+ await notificationService.TryAsync(
+ Task.Run(() => File.Delete(path)),
+ message: "Error deleting workflow"
+ );
+
+ var id = metadata?.Workflow?.Id;
+ if (!string.IsNullOrWhiteSpace(id))
+ {
+ workflowsCache.Remove(id);
+ }
+ }
+
+ notificationService.Show(
+ Resources.Label_WorkflowDeleted,
+ string.Format(Resources.Label_WorkflowDeletedSuccessfully, metadata?.Workflow?.Name)
+ );
+ }
+
+ private void OnWorkflowInstalled(object? sender, EventArgs e)
+ {
+ LoadInstalledWorkflowsAsync().SafeFireAndForget();
+ }
+
+ public void Dispose()
+ {
+ workflowsCache.Dispose();
+ EventManager.Instance.WorkflowInstalled -= OnWorkflowInstalled;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
index 5aec5637..2b77217a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
@@ -36,6 +37,7 @@ public partial class MainWindowViewModel : ViewModelBase
private readonly ITrackedDownloadService trackedDownloadService;
private readonly IDiscordRichPresenceService discordRichPresenceService;
private readonly IModelIndexService modelIndexService;
+ private readonly Lazy modelDownloadLinkHandler;
public string Greeting => "Welcome to Avalonia!";
[ObservableProperty]
@@ -71,7 +73,8 @@ public partial class MainWindowViewModel : ViewModelBase
IDiscordRichPresenceService discordRichPresenceService,
ServiceManager dialogFactory,
ITrackedDownloadService trackedDownloadService,
- IModelIndexService modelIndexService
+ IModelIndexService modelIndexService,
+ Lazy modelDownloadLinkHandler
)
{
this.settingsManager = settingsManager;
@@ -79,6 +82,7 @@ public partial class MainWindowViewModel : ViewModelBase
this.discordRichPresenceService = discordRichPresenceService;
this.trackedDownloadService = trackedDownloadService;
this.modelIndexService = modelIndexService;
+ this.modelDownloadLinkHandler = modelDownloadLinkHandler;
ProgressManagerViewModel = dialogFactory.Get();
UpdateViewModel = dialogFactory.Get();
}
@@ -107,6 +111,25 @@ public partial class MainWindowViewModel : ViewModelBase
return;
}
+ try
+ {
+ await modelDownloadLinkHandler.Value.StartListening();
+ }
+ catch (IOException)
+ {
+ var dialog = new BetterContentDialog
+ {
+ Title = Resources.Label_StabilityMatrixAlreadyRunning,
+ Content = Resources.Label_AnotherInstanceAlreadyRunning,
+ IsPrimaryButtonEnabled = true,
+ PrimaryButtonText = Resources.Action_Close,
+ DefaultButton = ContentDialogButton.Primary
+ };
+ await dialog.ShowAsync();
+ App.Shutdown();
+ return;
+ }
+
// Initialize Discord Rich Presence (this needs LibraryDir so is set here)
discordRichPresenceService.UpdateState();
@@ -139,14 +162,6 @@ public partial class MainWindowViewModel : ViewModelBase
Content = new NewOneClickInstallDialog { DataContext = viewModel },
};
- EventManager.Instance.OneClickInstallFinished += (_, skipped) =>
- {
- if (skipped)
- return;
-
- EventManager.Instance.OnTeachingTooltipNeeded();
- };
-
var firstDialogResult = await dialog.ShowAsync(App.TopLevel);
if (firstDialogResult != ContentDialogResult.Primary)
diff --git a/StabilityMatrix.Avalonia/ViewModels/NewCheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/NewCheckpointsPageViewModel.cs
index 45ea5ed0..ba95645c 100644
--- a/StabilityMatrix.Avalonia/ViewModels/NewCheckpointsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/NewCheckpointsPageViewModel.cs
@@ -14,6 +14,7 @@ using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using Refit;
using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
@@ -47,7 +48,7 @@ public partial class NewCheckpointsPageViewModel(
IMetadataImportService metadataImportService
) : PageViewModelBase
{
- public override string Title => "Checkpoint Manager";
+ public override string Title => Resources.Label_CheckpointManager;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Cellular5g, IsFilled = true };
diff --git a/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs
index d900f2bc..d96dac06 100644
--- a/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs
@@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
@@ -18,7 +19,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[Singleton]
public partial class NewPackageManagerViewModel : PageViewModelBase
{
- public override string Title => "Packages";
+ public override string Title => Resources.Label_Packages;
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
public IReadOnlyList SubPages { get; }
@@ -58,6 +59,10 @@ public partial class NewPackageManagerViewModel : PageViewModelBase
{
CurrentPagePath.Add(value);
}
+ else if (value is RunningPackageViewModel)
+ {
+ CurrentPagePath.Add(value);
+ }
else
{
CurrentPagePath.Clear();
diff --git a/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
new file mode 100644
index 00000000..de438ecc
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
@@ -0,0 +1,348 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using AsyncAwaitBestPractices;
+using Avalonia.Controls.Notifications;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using DynamicData;
+using DynamicData.Binding;
+using FluentAvalonia.UI.Controls;
+using Refit;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Avalonia.Services;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.ViewModels.Dialogs;
+using StabilityMatrix.Avalonia.Views;
+using StabilityMatrix.Core.Api;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Helper.Factory;
+using StabilityMatrix.Core.Models.Api.OpenArt;
+using StabilityMatrix.Core.Models.PackageModification;
+using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Services;
+using Resources = StabilityMatrix.Avalonia.Languages.Resources;
+
+namespace StabilityMatrix.Avalonia.ViewModels;
+
+[View(typeof(OpenArtBrowserPage))]
+[Singleton]
+public partial class OpenArtBrowserViewModel(
+ IOpenArtApi openArtApi,
+ INotificationService notificationService,
+ ISettingsManager settingsManager,
+ IPackageFactory packageFactory,
+ ServiceManager vmFactory
+) : TabViewModelBase, IInfinitelyScroll
+{
+ private const int PageSize = 20;
+
+ public override string Header => Resources.Label_OpenArtBrowser;
+
+ private readonly SourceCache searchResultsCache = new(x => x.Id);
+
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(PageCount), nameof(CanGoBack), nameof(CanGoForward), nameof(CanGoToEnd))]
+ private OpenArtSearchResponse? latestSearchResponse;
+
+ [ObservableProperty]
+ private IObservableCollection searchResults =
+ new ObservableCollectionExtended();
+
+ [ObservableProperty]
+ private string searchQuery = string.Empty;
+
+ [ObservableProperty]
+ private bool isLoading;
+
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(InternalPageNumber), nameof(CanGoBack))]
+ private int displayedPageNumber = 1;
+
+ public int InternalPageNumber => DisplayedPageNumber - 1;
+
+ public int PageCount =>
+ Math.Max(
+ 1,
+ Convert.ToInt32(Math.Ceiling((LatestSearchResponse?.Total ?? 0) / Convert.ToDouble(PageSize)))
+ );
+
+ public bool CanGoBack =>
+ string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && InternalPageNumber > 0;
+
+ public bool CanGoForward =>
+ !string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) || PageCount > InternalPageNumber + 1;
+
+ public bool CanGoToEnd =>
+ string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && PageCount > InternalPageNumber + 1;
+
+ public IEnumerable AllSortModes => ["Trending", "Latest", "Most Downloaded", "Most Liked"];
+
+ [ObservableProperty]
+ private string? selectedSortMode;
+
+ protected override void OnInitialLoaded()
+ {
+ searchResultsCache.Connect().DeferUntilLoaded().Bind(SearchResults).Subscribe();
+ SelectedSortMode = AllSortModes.First();
+ }
+
+ [RelayCommand]
+ private async Task FirstPage()
+ {
+ DisplayedPageNumber = 1;
+ searchResultsCache.Clear();
+
+ await DoSearch();
+ }
+
+ [RelayCommand]
+ private async Task PreviousPage()
+ {
+ DisplayedPageNumber--;
+ searchResultsCache.Clear();
+
+ await DoSearch(InternalPageNumber);
+ }
+
+ [RelayCommand]
+ private async Task NextPage()
+ {
+ if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
+ {
+ DisplayedPageNumber++;
+ }
+
+ searchResultsCache.Clear();
+ await DoSearch(InternalPageNumber);
+ }
+
+ [RelayCommand]
+ private async Task LastPage()
+ {
+ if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
+ {
+ DisplayedPageNumber = PageCount;
+ }
+
+ searchResultsCache.Clear();
+ await DoSearch(PageCount - 1);
+ }
+
+ [Localizable(false)]
+ [RelayCommand]
+ private void OpenModel(OpenArtSearchResult workflow)
+ {
+ ProcessRunner.OpenUrl($"https://openart.ai/workflows/{workflow.Id}");
+ }
+
+ [RelayCommand]
+ private async Task SearchButton()
+ {
+ DisplayedPageNumber = 1;
+ LatestSearchResponse = null;
+ searchResultsCache.Clear();
+
+ await DoSearch();
+ }
+
+ [RelayCommand]
+ private async Task OpenWorkflow(OpenArtSearchResult workflow)
+ {
+ var vm = new OpenArtWorkflowViewModel(settingsManager, packageFactory) { Workflow = workflow };
+
+ var dialog = new BetterContentDialog
+ {
+ IsPrimaryButtonEnabled = true,
+ IsSecondaryButtonEnabled = true,
+ PrimaryButtonText = Resources.Action_Import,
+ SecondaryButtonText = Resources.Action_Cancel,
+ DefaultButton = ContentDialogButton.Primary,
+ IsFooterVisible = true,
+ MaxDialogWidth = 750,
+ MaxDialogHeight = 850,
+ CloseOnClickOutside = true,
+ Content = vm
+ };
+
+ var result = await dialog.ShowAsync();
+
+ if (result != ContentDialogResult.Primary)
+ return;
+
+ List steps =
+ [
+ new DownloadOpenArtWorkflowStep(openArtApi, vm.Workflow, settingsManager)
+ ];
+
+ // Add install steps if missing nodes and preferred
+ if (
+ vm is
+ {
+ InstallRequiredNodes: true,
+ MissingNodes: { Count: > 0 } missingNodes,
+ SelectedPackage: not null,
+ SelectedPackagePair: not null
+ }
+ )
+ {
+ var extensionManager = vm.SelectedPackagePair.BasePackage.ExtensionManager!;
+
+ steps.AddRange(
+ missingNodes.Select(
+ extension =>
+ new InstallExtensionStep(
+ extensionManager,
+ vm.SelectedPackagePair.InstalledPackage,
+ extension
+ )
+ )
+ );
+ }
+
+ var runner = new PackageModificationRunner
+ {
+ ShowDialogOnStart = true,
+ ModificationCompleteTitle = Resources.Label_WorkflowImported,
+ ModificationCompleteMessage = Resources.Label_FinishedImportingWorkflow
+ };
+ EventManager.Instance.OnPackageInstallProgressAdded(runner);
+
+ await runner.ExecuteSteps(steps);
+
+ notificationService.Show(
+ Resources.Label_WorkflowImported,
+ Resources.Label_WorkflowImportComplete,
+ NotificationType.Success
+ );
+
+ EventManager.Instance.OnWorkflowInstalled();
+ }
+
+ [RelayCommand]
+ private void OpenOnOpenArt(OpenArtSearchResult? workflow)
+ {
+ if (workflow?.Id == null)
+ return;
+
+ ProcessRunner.OpenUrl($"https://openart.ai/workflows/{workflow.Id}");
+ }
+
+ private async Task DoSearch(int page = 0)
+ {
+ IsLoading = true;
+
+ try
+ {
+ OpenArtSearchResponse? response = null;
+ if (string.IsNullOrWhiteSpace(SearchQuery))
+ {
+ var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) };
+ if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
+ {
+ request.Cursor = LatestSearchResponse.NextCursor;
+ }
+
+ response = await openArtApi.GetFeedAsync(request);
+ }
+ else
+ {
+ response = await openArtApi.SearchAsync(
+ new OpenArtSearchRequest
+ {
+ Keyword = SearchQuery,
+ PageSize = PageSize,
+ CurrentPage = page
+ }
+ );
+ }
+
+ foreach (var item in response.Items)
+ {
+ searchResultsCache.AddOrUpdate(item);
+ }
+
+ LatestSearchResponse = response;
+ }
+ catch (ApiException e)
+ {
+ notificationService.Show(Resources.Label_ErrorRetrievingWorkflows, e.Message);
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+
+ partial void OnSelectedSortModeChanged(string? value)
+ {
+ if (value is null || SearchResults.Count == 0)
+ return;
+
+ searchResultsCache.Clear();
+ LatestSearchResponse = null;
+
+ DoSearch().SafeFireAndForget();
+ }
+
+ public async Task LoadNextPageAsync()
+ {
+ if (!CanGoForward)
+ return;
+
+ try
+ {
+ OpenArtSearchResponse? response = null;
+ if (string.IsNullOrWhiteSpace(SearchQuery))
+ {
+ var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) };
+ if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
+ {
+ request.Cursor = LatestSearchResponse.NextCursor;
+ }
+
+ response = await openArtApi.GetFeedAsync(request);
+ }
+ else
+ {
+ DisplayedPageNumber++;
+ response = await openArtApi.SearchAsync(
+ new OpenArtSearchRequest
+ {
+ Keyword = SearchQuery,
+ PageSize = PageSize,
+ CurrentPage = InternalPageNumber
+ }
+ );
+ }
+
+ foreach (var item in response.Items)
+ {
+ searchResultsCache.AddOrUpdate(item);
+ }
+
+ LatestSearchResponse = response;
+ }
+ catch (ApiException e)
+ {
+ notificationService.Show("Unable to load the next page", e.Message);
+ }
+ }
+
+ private static string GetSortMode(string? sortMode)
+ {
+ return sortMode switch
+ {
+ "Trending" => "trending",
+ "Latest" => "latest",
+ "Most Downloaded" => "most_downloaded",
+ "Most Liked" => "most_liked",
+ _ => "trending"
+ };
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
index ecb81a55..da48a2e8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
@@ -5,6 +5,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
+using System.Threading;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using AsyncImageLoader;
@@ -28,8 +29,6 @@ using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.OutputsPage;
using StabilityMatrix.Core.Attributes;
-using StabilityMatrix.Core.Exceptions;
-using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
@@ -52,19 +51,21 @@ public partial class OutputsPageViewModel : PageViewModelBase
private readonly INotificationService notificationService;
private readonly INavigationService navigationService;
private readonly ILogger logger;
+ private readonly List cancellationTokenSources = [];
+
public override string Title => Resources.Label_OutputsPageTitle;
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
public SourceCache OutputsCache { get; } = new(file => file.AbsolutePath);
+ private SourceCache categoriesCache = new(category => category.Path);
+
public IObservableCollection Outputs { get; set; } =
new ObservableCollectionExtended();
- public IEnumerable OutputTypes { get; } = Enum.GetValues();
-
- [ObservableProperty]
- private ObservableCollection categories;
+ public IObservableCollection Categories { get; set; } =
+ new ObservableCollectionExtended();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanShowOutputTypes))]
@@ -86,6 +87,15 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty]
private bool isConsolidating;
+ [ObservableProperty]
+ private bool isLoading;
+
+ [ObservableProperty]
+ private bool showFolders;
+
+ [ObservableProperty]
+ private bool isChangingCategory;
+
public bool CanShowOutputTypes => SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
public string NumImagesSelected =>
@@ -95,6 +105,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
private string[] allowedExtensions = [".png", ".webp"];
+ private PackageOutputCategory? lastOutputCategory;
+
public OutputsPageViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory,
@@ -113,7 +125,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
// Observable predicate from SearchQuery changes
var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
- .Throttle(TimeSpan.FromMilliseconds(50))!
+ .Throttle(TimeSpan.FromMilliseconds(100))!
.Select(property => searcher.GetPredicate(property.Value))
.AsObservable();
@@ -122,28 +134,37 @@ public partial class OutputsPageViewModel : PageViewModelBase
.DeferUntilLoaded()
.Filter(searchPredicate)
.Transform(file => new OutputImageViewModel(file))
- .SortBy(vm => vm.ImageFile.CreatedAt, SortDirection.Descending)
+ .Sort(
+ SortExpressionComparer
+ .Descending(vm => vm.ImageFile.CreatedAt)
+ .ThenByDescending(vm => vm.ImageFile.FileName)
+ )
.Bind(Outputs)
.WhenPropertyChanged(p => p.IsSelected)
+ .Throttle(TimeSpan.FromMilliseconds(50))
.Subscribe(_ =>
{
NumItemsSelected = Outputs.Count(o => o.IsSelected);
});
+ categoriesCache.Connect().DeferUntilLoaded().Bind(Categories).Subscribe();
+
settingsManager.RelayPropertyFor(
this,
vm => vm.ImageSize,
settings => settings.OutputsImageSize,
delay: TimeSpan.FromMilliseconds(250)
);
- }
- protected override void OnInitialLoaded()
- {
- RefreshCategories();
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.ShowFolders,
+ settings => settings.IsOutputsTreeViewEnabled,
+ true
+ );
}
- public override void OnLoaded()
+ protected override void OnInitialLoaded()
{
if (Design.IsDesignMode)
return;
@@ -151,14 +172,17 @@ public partial class OutputsPageViewModel : PageViewModelBase
if (!settingsManager.IsLibraryDirSet)
return;
- base.OnLoaded();
-
Directory.CreateDirectory(settingsManager.ImagesDirectory);
+ RefreshCategories();
+
SelectedCategory ??= Categories.First();
SelectedOutputType ??= SharedOutputType.All;
SearchQuery = string.Empty;
ImageSize = settingsManager.Settings.OutputsImageSize;
+ lastOutputCategory = SelectedCategory;
+
+ IsChangingCategory = true;
var path =
CanShowOutputTypes && SelectedOutputType != SharedOutputType.All
@@ -169,7 +193,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
partial void OnSelectedCategoryChanged(PackageOutputCategory? oldValue, PackageOutputCategory? newValue)
{
- if (oldValue == newValue || newValue == null)
+ if (oldValue == newValue || oldValue == null || newValue == null)
return;
var path =
@@ -177,11 +201,12 @@ public partial class OutputsPageViewModel : PageViewModelBase
? Path.Combine(newValue.Path, SelectedOutputType.ToString())
: SelectedCategory.Path;
GetOutputs(path);
+ lastOutputCategory = newValue;
}
partial void OnSelectedOutputTypeChanged(SharedOutputType? oldValue, SharedOutputType? newValue)
{
- if (oldValue == newValue || newValue == null)
+ if (oldValue == newValue || oldValue == null || newValue == null)
return;
var path =
@@ -262,8 +287,13 @@ public partial class OutputsPageViewModel : PageViewModelBase
public void Refresh()
{
- Dispatcher.UIThread.Post(() => RefreshCategories());
- Dispatcher.UIThread.Post(OnLoaded);
+ Dispatcher.UIThread.Post(RefreshCategories);
+
+ var path =
+ CanShowOutputTypes && SelectedOutputType != SharedOutputType.All
+ ? Path.Combine(SelectedCategory.Path, SelectedOutputType.ToString())
+ : SelectedCategory.Path;
+ GetOutputs(path);
}
public async Task DeleteImage(OutputImageViewModel? item)
@@ -273,8 +303,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
var confirmationDialog = new BetterContentDialog
{
- Title = "Are you sure you want to delete this image?",
- Content = "This action cannot be undone.",
+ Title = Resources.Label_AreYouSure,
+ Content = Resources.Label_ActionCannotBeUndone,
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
@@ -352,8 +382,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
{
var confirmationDialog = new BetterContentDialog
{
- Title = $"Are you sure you want to delete {NumItemsSelected} images?",
- Content = "This action cannot be undone.",
+ Title = string.Format(Resources.Label_AreYouSureDeleteImages, NumItemsSelected),
+ Content = Resources.Label_ActionCannotBeUndone,
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
@@ -365,12 +395,13 @@ public partial class OutputsPageViewModel : PageViewModelBase
var selected = Outputs.Where(o => o.IsSelected).ToList();
Debug.Assert(selected.Count == NumItemsSelected);
+
+ var imagesToRemove = new List();
foreach (var output in selected)
{
// Delete the file
var imageFile = new FilePath(output.ImageFile.AbsolutePath);
var result = await notificationService.TryAsync(imageFile.DeleteAsync());
-
if (!result.IsSuccessful)
{
continue;
@@ -383,15 +414,10 @@ public partial class OutputsPageViewModel : PageViewModelBase
await notificationService.TryAsync(sideCar.DeleteAsync());
}
- OutputsCache.Remove(output.ImageFile);
-
- // Invalidate cache
- if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
- {
- loader.RemoveAllNamesFromCache(imageFile.Name);
- }
+ imagesToRemove.Add(output.ImageFile);
}
+ OutputsCache.Remove(imagesToRemove);
NumItemsSelected = 0;
ClearSelection();
}
@@ -491,7 +517,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
}
}
- OnLoaded();
+ Refresh();
IsConsolidating = false;
}
@@ -517,20 +543,58 @@ public partial class OutputsPageViewModel : PageViewModelBase
return;
}
- var files = Directory
- .EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
- .Where(path => allowedExtensions.Contains(new FilePath(path).Extension))
- .Select(file => LocalImageFile.FromPath(file))
- .ToList();
-
- if (files.Count == 0)
+ if (lastOutputCategory?.Path.Equals(directory) is not true)
{
OutputsCache.Clear();
+ IsChangingCategory = true;
}
- else
+
+ IsLoading = true;
+
+ cancellationTokenSources.ForEach(cts => cts.Cancel());
+
+ Task.Run(() =>
{
- OutputsCache.EditDiff(files);
- }
+ var getOutputsTokenSource = new CancellationTokenSource();
+ cancellationTokenSources.Add(getOutputsTokenSource);
+
+ if (getOutputsTokenSource.IsCancellationRequested)
+ {
+ cancellationTokenSources.Remove(getOutputsTokenSource);
+ return;
+ }
+
+ var files = Directory
+ .EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
+ .Where(file => allowedExtensions.Contains(new FilePath(file).Extension))
+ .Select(file => LocalImageFile.FromPath(file))
+ .ToList();
+
+ if (getOutputsTokenSource.IsCancellationRequested)
+ {
+ cancellationTokenSources.Remove(getOutputsTokenSource);
+ return;
+ }
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (files.Count == 0 && OutputsCache.Count == 0)
+ {
+ IsLoading = false;
+ IsChangingCategory = false;
+ return;
+ }
+
+ OutputsCache.EditDiff(
+ files,
+ (oldItem, newItem) => oldItem.AbsolutePath == newItem.AbsolutePath
+ );
+
+ IsLoading = false;
+ IsChangingCategory = false;
+ });
+ cancellationTokenSources.Remove(getOutputsTokenSource);
+ });
}
private void RefreshCategories()
@@ -559,7 +623,10 @@ public partial class OutputsPageViewModel : PageViewModelBase
pair.InstalledPackage.FullPath!,
pair.BasePackage.OutputFolderName
),
- Name = pair.InstalledPackage.DisplayName ?? ""
+ Name = pair.InstalledPackage.DisplayName ?? "",
+ SubDirectories = GetSubfolders(
+ Path.Combine(pair.InstalledPackage.FullPath!, pair.BasePackage.OutputFolderName)
+ )
}
)
.ToList();
@@ -569,18 +636,37 @@ public partial class OutputsPageViewModel : PageViewModelBase
new PackageOutputCategory
{
Path = settingsManager.ImagesDirectory,
- Name = "Shared Output Folder"
+ Name = "Shared Output Folder",
+ SubDirectories = GetSubfolders(settingsManager.ImagesDirectory)
}
);
- packageCategories.Insert(
- 1,
- new PackageOutputCategory { Path = settingsManager.ImagesInferenceDirectory, Name = "Inference" }
- );
+ categoriesCache.EditDiff(packageCategories, (a, b) => a.Path == b.Path);
+
+ SelectedCategory = previouslySelectedCategory ?? Categories.First();
+ }
+
+ private ObservableCollection GetSubfolders(string strPath)
+ {
+ var subfolders = new ObservableCollection();
+
+ if (!Directory.Exists(strPath))
+ return subfolders;
+
+ var directories = Directory.EnumerateDirectories(strPath, "*", SearchOption.TopDirectoryOnly);
+
+ foreach (var dir in directories)
+ {
+ var category = new PackageOutputCategory { Name = Path.GetFileName(dir), Path = dir };
- Categories = new ObservableCollection(packageCategories);
+ if (Directory.GetDirectories(dir, "*", SearchOption.TopDirectoryOnly).Length > 0)
+ {
+ category.SubDirectories = GetSubfolders(dir);
+ }
+
+ subfolders.Add(category);
+ }
- SelectedCategory =
- Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name) ?? Categories.First();
+ return subfolders;
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
index 67426208..71d624ec 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
@@ -1,11 +1,16 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Collections.Specialized;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
+using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
+using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
@@ -26,7 +31,6 @@ using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages;
-using StabilityMatrix.Core.Models.Settings;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
@@ -34,20 +38,23 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
[ManagedService]
[Transient]
-public partial class PackageCardViewModel : ProgressViewModel
+public partial class PackageCardViewModel(
+ ILogger logger,
+ IPackageFactory packageFactory,
+ INotificationService notificationService,
+ ISettingsManager settingsManager,
+ INavigationService navigationService,
+ ServiceManager vmFactory,
+ RunningPackageService runningPackageService
+) : ProgressViewModel
{
- private readonly ILogger logger;
- private readonly IPackageFactory packageFactory;
- private readonly INotificationService notificationService;
- private readonly ISettingsManager settingsManager;
- private readonly INavigationService navigationService;
- private readonly ServiceManager vmFactory;
+ private string webUiUrl = string.Empty;
[ObservableProperty]
private InstalledPackage? package;
[ObservableProperty]
- private string? cardImageSource;
+ private Uri? cardImageSource;
[ObservableProperty]
private bool isUpdateAvailable;
@@ -82,21 +89,31 @@ public partial class PackageCardViewModel : ProgressViewModel
[ObservableProperty]
private bool canUseExtensions;
- public PackageCardViewModel(
- ILogger logger,
- IPackageFactory packageFactory,
- INotificationService notificationService,
- ISettingsManager settingsManager,
- INavigationService navigationService,
- ServiceManager vmFactory
- )
+ [ObservableProperty]
+ private bool isRunning;
+
+ [ObservableProperty]
+ private bool showWebUiButton;
+
+ private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
- this.logger = logger;
- this.packageFactory = packageFactory;
- this.notificationService = notificationService;
- this.settingsManager = settingsManager;
- this.navigationService = navigationService;
- this.vmFactory = vmFactory;
+ if (runningPackageService.RunningPackages.Select(x => x.Value) is not { } runningPackages)
+ return;
+
+ var runningViewModel = runningPackages.FirstOrDefault(
+ x => x.RunningPackage.InstalledPackage.Id == Package?.Id
+ );
+ if (runningViewModel is not null)
+ {
+ IsRunning = true;
+ runningViewModel.RunningPackage.BasePackage.Exited += BasePackageOnExited;
+ runningViewModel.RunningPackage.BasePackage.StartupComplete += RunningPackageOnStartupComplete;
+ }
+ else if (runningViewModel is null && IsRunning)
+ {
+ IsRunning = false;
+ ShowWebUiButton = false;
+ }
}
partial void OnPackageChanged(InstalledPackage? value)
@@ -110,7 +127,7 @@ public partial class PackageCardViewModel : ProgressViewModel
)
{
IsUnknownPackage = true;
- CardImageSource = "";
+ CardImageSource = null;
InstalledVersion = "Unknown";
}
else
@@ -118,7 +135,7 @@ public partial class PackageCardViewModel : ProgressViewModel
IsUnknownPackage = false;
var basePackage = packageFactory[value.PackageName];
- CardImageSource = basePackage?.PreviewImageUri.ToString() ?? Assets.NoImage.ToString();
+ CardImageSource = basePackage?.PreviewImageUri ?? Assets.NoImage;
InstalledVersion = value.Version?.DisplayVersion ?? "Unknown";
CanUseConfigMethod =
basePackage?.AvailableSharedFolderMethods.Contains(SharedFolderMethod.Configuration) ?? false;
@@ -127,11 +144,30 @@ public partial class PackageCardViewModel : ProgressViewModel
UseSharedOutput = Package?.UseSharedOutputFolder ?? false;
CanUseSharedOutput = basePackage?.SharedOutputFolders != null;
CanUseExtensions = basePackage?.SupportsExtensions ?? false;
+
+ runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged;
+ EventManager.Instance.PackageRelaunchRequested += InstanceOnPackageRelaunchRequested;
}
}
+ private void InstanceOnPackageRelaunchRequested(object? sender, InstalledPackage e)
+ {
+ if (e.Id != Package?.Id)
+ return;
+
+ navigationService.GoBack();
+ Launch().SafeFireAndForget();
+ }
+
public override async Task OnLoadedAsync()
{
+ if (Design.IsDesignMode && Package?.DisplayName == "Running Comfy")
+ {
+ IsRunning = true;
+ IsUpdateAvailable = true;
+ ShowWebUiButton = true;
+ }
+
if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet || Package is not { } currentPackage)
return;
@@ -160,18 +196,122 @@ public partial class PackageCardViewModel : ProgressViewModel
}
IsUpdateAvailable = await HasUpdate();
+
+ if (
+ Package != null
+ && !IsRunning
+ && runningPackageService.RunningPackages.TryGetValue(Package.Id, out var runningPackageVm)
+ )
+ {
+ IsRunning = true;
+ runningPackageVm.RunningPackage.BasePackage.Exited += BasePackageOnExited;
+ runningPackageVm.RunningPackage.BasePackage.StartupComplete +=
+ RunningPackageOnStartupComplete;
+ webUiUrl = runningPackageVm.WebUiUrl;
+ ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);
+ }
+ }
+ }
+
+ public override void OnUnloaded()
+ {
+ EventManager.Instance.PackageRelaunchRequested -= InstanceOnPackageRelaunchRequested;
+ runningPackageService.RunningPackages.CollectionChanged -= RunningPackagesOnCollectionChanged;
+ }
+
+ public async Task Launch()
+ {
+ if (Package == null)
+ return;
+
+ var packagePair = await runningPackageService.StartPackage(Package);
+
+ if (packagePair != null)
+ {
+ IsRunning = true;
+
+ packagePair.BasePackage.Exited += BasePackageOnExited;
+ packagePair.BasePackage.StartupComplete += RunningPackageOnStartupComplete;
+
+ var vm = runningPackageService.GetRunningPackageViewModel(packagePair.InstalledPackage.Id);
+ if (vm != null)
+ {
+ navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
+ }
}
+
+ // settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);
+ //
+ // navigationService.NavigateTo(new BetterDrillInNavigationTransition());
+ // EventManager.Instance.OnPackageLaunchRequested(Package.Id);
}
- public void Launch()
+ public void NavToConsole()
{
if (Package == null)
return;
- settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);
+ var vm = runningPackageService.GetRunningPackageViewModel(Package.Id);
+ if (vm != null)
+ {
+ navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
+ }
+ }
+
+ public void LaunchWebUi()
+ {
+ if (string.IsNullOrEmpty(webUiUrl))
+ return;
- navigationService.NavigateTo(new BetterDrillInNavigationTransition());
- EventManager.Instance.OnPackageLaunchRequested(Package.Id);
+ notificationService.TryAsync(
+ Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)),
+ "Failed to open URL",
+ $"{webUiUrl}"
+ );
+ }
+
+ private void BasePackageOnExited(object? sender, int exitCode)
+ {
+ Dispatcher
+ .UIThread.InvokeAsync(async () =>
+ {
+ logger.LogTrace("Process exited ({Code}) at {Time:g}", exitCode, DateTimeOffset.Now);
+
+ // Need to wait for streams to finish before detaching handlers
+ if (sender is BaseGitPackage { VenvRunner: not null } package)
+ {
+ var process = package.VenvRunner.Process;
+ if (process is not null)
+ {
+ // Max 5 seconds
+ var ct = new CancellationTokenSource(5000).Token;
+ try
+ {
+ await process.WaitUntilOutputEOF(ct);
+ }
+ catch (OperationCanceledException e)
+ {
+ logger.LogWarning("Waiting for process EOF timed out: {Message}", e.Message);
+ }
+ }
+ }
+
+ // Detach handlers
+ if (sender is BasePackage basePackage)
+ {
+ basePackage.Exited -= BasePackageOnExited;
+ basePackage.StartupComplete -= RunningPackageOnStartupComplete;
+ }
+
+ if (Package?.Id != null)
+ {
+ runningPackageService.RunningPackages.Remove(Package.Id);
+ }
+
+ IsRunning = false;
+ ShowWebUiButton = false;
+ })
+ .SafeFireAndForget();
}
public async Task Uninstall()
@@ -286,6 +426,21 @@ public partial class PackageCardViewModel : ProgressViewModel
versionOptions.CommitHash = latest.Sha;
}
+ var confirmationDialog = new BetterContentDialog
+ {
+ Title = Resources.Label_AreYouSure,
+ Content =
+ $"{Package.DisplayName} will be updated to the latest version ({versionOptions.GetReadableVersionString()})",
+ PrimaryButtonText = Resources.Action_Continue,
+ SecondaryButtonText = Resources.Action_Cancel,
+ DefaultButton = ContentDialogButton.Primary,
+ IsSecondaryButtonEnabled = true,
+ };
+
+ var dialogResult = await confirmationDialog.ShowAsync();
+ if (dialogResult != ContentDialogResult.Primary)
+ return;
+
var updatePackageStep = new UpdatePackageStep(
settingsManager,
Package,
@@ -439,6 +594,64 @@ public partial class PackageCardViewModel : ProgressViewModel
ProcessRunner.OpenUrl(basePackage.GithubUrl);
}
+ [RelayCommand]
+ private async Task Stop()
+ {
+ if (Package is null)
+ return;
+
+ await runningPackageService.StopPackage(Package.Id);
+ IsRunning = false;
+ ShowWebUiButton = false;
+ }
+
+ [RelayCommand]
+ private async Task Restart()
+ {
+ await Stop();
+ await Launch();
+ }
+
+ [RelayCommand]
+ private async Task ShowLaunchOptions()
+ {
+ var basePackage = packageFactory.FindPackageByName(Package?.PackageName);
+ if (basePackage == null)
+ {
+ logger.LogWarning("Package {Name} not found", Package?.PackageName);
+ return;
+ }
+
+ var viewModel = vmFactory.Get();
+ viewModel.Cards = LaunchOptionCard
+ .FromDefinitions(basePackage.LaunchOptions, Package?.LaunchArgs ?? [])
+ .ToImmutableArray();
+
+ logger.LogDebug("Launching config dialog with cards: {CardsCount}", viewModel.Cards.Count);
+
+ var dialog = new BetterContentDialog
+ {
+ ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
+ IsPrimaryButtonEnabled = true,
+ PrimaryButtonText = Resources.Action_Save,
+ CloseButtonText = Resources.Action_Cancel,
+ FullSizeDesired = true,
+ DefaultButton = ContentDialogButton.Primary,
+ ContentMargin = new Thickness(32, 16),
+ Padding = new Thickness(0, 16),
+ Content = new LaunchOptionsDialog { DataContext = viewModel, }
+ };
+
+ var result = await dialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary && Package != null)
+ {
+ // Save config
+ var args = viewModel.AsLaunchArgs();
+ settingsManager.SaveLaunchArgs(Package.Id, args);
+ }
+ }
+
private async Task HasUpdate()
{
if (Package == null || IsUnknownPackage || Design.IsDesignMode)
@@ -569,4 +782,10 @@ public partial class PackageCardViewModel : ProgressViewModel
IsSharedModelConfig = false;
}
}
+
+ private void RunningPackageOnStartupComplete(object? sender, string e)
+ {
+ webUiUrl = e.Replace("0.0.0.0", "127.0.0.1");
+ ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);
+ }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
index 71e14b31..6c24efc2 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
@@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Avalonia.Models.PackageSteps;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -85,6 +86,9 @@ public partial class PackageInstallDetailViewModel(
[ObservableProperty]
private GitCommit? selectedCommit;
+ [ObservableProperty]
+ private bool isOutputSharingEnabled = true;
+
[ObservableProperty]
private bool canInstall;
@@ -97,6 +101,8 @@ public partial class PackageInstallDetailViewModel(
OnInstallNameChanged(InstallName);
+ CanInstall = false;
+
SelectedTorchVersion = SelectedPackage.GetRecommendedTorchVersion();
SelectedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod;
@@ -183,6 +189,7 @@ public partial class PackageInstallDetailViewModel(
}
var prereqStep = new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, SelectedPackage);
+ var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(Path.Combine(installLocation, "venv"));
var downloadOptions = new DownloadPackageVersionOptions();
var installedVersion = new InstalledPackageVersion();
@@ -224,6 +231,8 @@ public partial class PackageInstallDetailViewModel(
installLocation
);
+ var setupOutputSharingStep = new SetupOutputSharingStep(SelectedPackage, installLocation);
+
var package = new InstalledPackage
{
DisplayName = InstallName,
@@ -234,7 +243,8 @@ public partial class PackageInstallDetailViewModel(
LaunchCommand = SelectedPackage.LaunchCommand,
LastUpdateCheck = DateTimeOffset.Now,
PreferredTorchVersion = SelectedTorchVersion,
- PreferredSharedFolderMethod = SelectedSharedFolderMethod
+ PreferredSharedFolderMethod = SelectedSharedFolderMethod,
+ UseSharedOutputFolder = IsOutputSharingEnabled
};
var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, package);
@@ -244,11 +254,17 @@ public partial class PackageInstallDetailViewModel(
setPackageInstallingStep,
prereqStep,
downloadStep,
+ unpackSiteCustomizeStep,
installStep,
setupModelFoldersStep,
addInstalledPackageStep
};
+ if (IsOutputSharingEnabled)
+ {
+ steps.Insert(steps.IndexOf(addInstalledPackageStep), setupOutputSharingStep);
+ }
+
var packageName = SelectedPackage.Name;
var runner = new PackageModificationRunner
@@ -281,7 +297,7 @@ public partial class PackageInstallDetailViewModel(
SelectedVersion = !IsReleaseMode
? AvailableVersions?.FirstOrDefault(x => x.TagName == SelectedPackage.MainBranch)
?? AvailableVersions?.FirstOrDefault()
- : AvailableVersions?.FirstOrDefault();
+ : AvailableVersions?.FirstOrDefault(v => !v.IsPrerelease);
CanInstall = !ShowDuplicateWarning;
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
index 6066fd02..9d59df05 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
@@ -6,8 +6,6 @@ using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
-using Avalonia.Controls.Notifications;
-using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
@@ -15,18 +13,15 @@ using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
-using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
-using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.PackageManager;
using StabilityMatrix.Avalonia.Views;
-using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
-using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Services;
using Symbol = FluentIcons.Common.Symbol;
@@ -48,7 +43,7 @@ public partial class PackageManagerViewModel : PageViewModelBase
private readonly INavigationService packageNavigationService;
private readonly ILogger logger;
- public override string Title => "Packages";
+ public override string Title => Resources.Label_Packages;
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
///
@@ -84,6 +79,7 @@ public partial class PackageManagerViewModel : PageViewModelBase
this.logger = logger;
EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;
+ EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;
var installed = installedPackages.Connect();
var unknown = unknownInstalledPackages.Connect();
@@ -107,6 +103,11 @@ public partial class PackageManagerViewModel : PageViewModelBase
timer.Tick += async (_, _) => await CheckPackagesForUpdates();
}
+ private void OnOneClickInstallFinished(object? sender, bool e)
+ {
+ OnLoadedAsync().SafeFireAndForget();
+ }
+
public void SetPackages(IEnumerable packages)
{
installedPackages.Edit(s => s.Load(packages));
@@ -119,7 +120,7 @@ public partial class PackageManagerViewModel : PageViewModelBase
public override async Task OnLoadedAsync()
{
- if (Design.IsDesignMode)
+ if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet)
return;
installedPackages.EditDiff(settingsManager.Settings.InstalledPackages, InstalledPackage.Comparer);
diff --git a/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs
index cd4f5f46..fb74fc17 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs
@@ -49,10 +49,21 @@ public class PackageInstallProgressItemViewModel : ProgressItemViewModelBase
Name = packageModificationRunner.CurrentStep?.ProgressTitle;
Failed = packageModificationRunner.Failed;
- if (string.IsNullOrWhiteSpace(e.Message) || e.Message.Contains("Downloading..."))
+ if (e.ProcessOutput == null && string.IsNullOrWhiteSpace(e.Message))
return;
- Progress.Console.PostLine(e.Message);
+ if (!string.IsNullOrWhiteSpace(e.Message) && e.Message.Contains("Downloading..."))
+ return;
+
+ if (e.ProcessOutput != null)
+ {
+ Progress.Console.Post(e.ProcessOutput.Value);
+ }
+ else
+ {
+ Progress.Console.PostLine(e.Message);
+ }
+
EventManager.Instance.OnScrollToBottomRequested();
if (
diff --git a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs
new file mode 100644
index 00000000..73f89a8b
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.Services;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.Views;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Packages;
+using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Services;
+using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;
+using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;
+
+namespace StabilityMatrix.Avalonia.ViewModels;
+
+[View(typeof(ConsoleOutputPage))]
+public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, IAsyncDisposable
+{
+ private readonly ISettingsManager settingsManager;
+ private readonly INotificationService notificationService;
+ private readonly RunningPackageService runningPackageService;
+
+ public PackagePair RunningPackage { get; }
+ public ConsoleViewModel Console { get; }
+ public override string Title => RunningPackage.InstalledPackage.PackageName ?? "Running Package";
+ public override IconSource IconSource => new SymbolIconSource();
+
+ [ObservableProperty]
+ private bool autoScrollToEnd;
+
+ [ObservableProperty]
+ private bool showWebUiButton;
+
+ [ObservableProperty]
+ private string webUiUrl = string.Empty;
+
+ [ObservableProperty]
+ private bool isRunning = true;
+
+ [ObservableProperty]
+ private string consoleInput = string.Empty;
+
+ [ObservableProperty]
+ private bool showWebUiTeachingTip;
+
+ ///
+ public RunningPackageViewModel(
+ ISettingsManager settingsManager,
+ INotificationService notificationService,
+ RunningPackageService runningPackageService,
+ PackagePair runningPackage,
+ ConsoleViewModel console
+ )
+ {
+ this.settingsManager = settingsManager;
+ this.notificationService = notificationService;
+ this.runningPackageService = runningPackageService;
+
+ RunningPackage = runningPackage;
+ Console = console;
+ Console.Document.LineCountChanged += DocumentOnLineCountChanged;
+ RunningPackage.BasePackage.StartupComplete += BasePackageOnStartupComplete;
+ RunningPackage.BasePackage.Exited += BasePackageOnExited;
+
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.AutoScrollToEnd,
+ settings => settings.AutoScrollLaunchConsoleToEnd,
+ true
+ );
+ }
+
+ private void BasePackageOnExited(object? sender, int e)
+ {
+ IsRunning = false;
+ ShowWebUiButton = false;
+ Console.Document.LineCountChanged -= DocumentOnLineCountChanged;
+ RunningPackage.BasePackage.StartupComplete -= BasePackageOnStartupComplete;
+ RunningPackage.BasePackage.Exited -= BasePackageOnExited;
+ runningPackageService.RunningPackages.Remove(RunningPackage.InstalledPackage.Id);
+ }
+
+ private void BasePackageOnStartupComplete(object? sender, string url)
+ {
+ WebUiUrl = url.Replace("0.0.0.0", "127.0.0.1");
+ ShowWebUiButton = !string.IsNullOrWhiteSpace(WebUiUrl);
+
+ if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.WebUiButtonMovedTip))
+ return;
+
+ ShowWebUiTeachingTip = true;
+ settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.WebUiButtonMovedTip));
+ }
+
+ private void DocumentOnLineCountChanged(object? sender, EventArgs e)
+ {
+ if (AutoScrollToEnd)
+ {
+ EventManager.Instance.OnScrollToBottomRequested();
+ }
+ }
+
+ [RelayCommand]
+ private async Task Restart()
+ {
+ await Stop();
+ await Task.Delay(100);
+ LaunchPackage();
+ }
+
+ [RelayCommand]
+ private void LaunchPackage()
+ {
+ EventManager.Instance.OnPackageRelaunchRequested(RunningPackage.InstalledPackage);
+ }
+
+ [RelayCommand]
+ private async Task Stop()
+ {
+ IsRunning = false;
+ await runningPackageService.StopPackage(RunningPackage.InstalledPackage.Id);
+ Console.PostLine($"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}");
+ await Console.StopUpdatesAsync();
+ }
+
+ [RelayCommand]
+ private void LaunchWebUi()
+ {
+ if (string.IsNullOrEmpty(WebUiUrl))
+ return;
+
+ notificationService.TryAsync(
+ Task.Run(() => ProcessRunner.OpenUrl(WebUiUrl)),
+ "Failed to open URL",
+ $"{WebUiUrl}"
+ );
+ }
+
+ [RelayCommand]
+ private async Task SendToConsole()
+ {
+ Console.PostLine(ConsoleInput);
+ if (RunningPackage?.BasePackage is BaseGitPackage gitPackage)
+ {
+ var venv = gitPackage.VenvRunner;
+ var process = venv?.Process;
+ if (process is not null)
+ {
+ await process.StandardInput.WriteLineAsync(ConsoleInput);
+ }
+ }
+
+ ConsoleInput = string.Empty;
+ }
+
+ public void Dispose()
+ {
+ Console.Dispose();
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await Console.DisposeAsync();
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index 55b6c896..c02c396b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -14,6 +14,7 @@ using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
+using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
@@ -129,6 +130,9 @@ public partial class MainSettingsViewModel : PageViewModelBase
[ObservableProperty]
private HolidayMode holidayModeSetting;
+ [ObservableProperty]
+ private bool infinitelyScrollWorkflowBrowser;
+
#region System Info
private static Lazy> GpuInfosLazy { get; } =
@@ -217,6 +221,13 @@ public partial class MainSettingsViewModel : PageViewModelBase
settings => settings.HolidayModeSetting
);
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.InfinitelyScrollWorkflowBrowser,
+ settings => settings.IsWorkflowInfiniteScrollEnabled,
+ true
+ );
+
DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);
hardwareInfoUpdateTimer.Tick += OnHardwareInfoUpdateTimerTick;
@@ -354,7 +365,7 @@ public partial class MainSettingsViewModel : PageViewModelBase
var viewModel = dialogFactory.Get();
// Load current settings
- var current = settingsManager.Settings.EnvironmentVariables ?? new Dictionary();
+ var current = settingsManager.Settings.UserEnvironmentVariables ?? new Dictionary();
viewModel.EnvVars = new ObservableCollection(
current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value))
);
@@ -374,7 +385,7 @@ public partial class MainSettingsViewModel : PageViewModelBase
.EnvVars.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key))
.GroupBy(kvp => kvp.Key, StringComparer.Ordinal)
.ToDictionary(g => g.Key, g => g.First().Value, StringComparer.Ordinal);
- settingsManager.Transaction(s => s.EnvironmentVariables = newEnvVars);
+ settingsManager.Transaction(s => s.UserEnvironmentVariables = newEnvVars);
}
}
@@ -774,7 +785,10 @@ public partial class MainSettingsViewModel : PageViewModelBase
[
new CommandItem(DebugFindLocalModelFromIndexCommand),
new CommandItem(DebugExtractDmgCommand),
- new CommandItem(DebugShowNativeNotificationCommand)
+ new CommandItem(DebugShowNativeNotificationCommand),
+ new CommandItem(DebugClearImageCacheCommand),
+ new CommandItem(DebugGCCollectCommand),
+ new CommandItem(DebugExtractImagePromptsToTxtCommand)
];
[RelayCommand]
@@ -889,6 +903,60 @@ public partial class MainSettingsViewModel : PageViewModelBase
);
}
+ [RelayCommand]
+ private void DebugClearImageCache()
+ {
+ if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
+ {
+ loader.ClearCache();
+ }
+ }
+
+ [RelayCommand]
+ private void DebugGCCollect()
+ {
+ GC.Collect();
+ }
+
+ [RelayCommand]
+ private async Task DebugExtractImagePromptsToTxt()
+ {
+ // Choose images
+ var provider = App.StorageProvider;
+ var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions { AllowMultiple = true });
+
+ if (files.Count == 0)
+ return;
+
+ var images = await Task.Run(
+ () => files.Select(f => LocalImageFile.FromPath(f.TryGetLocalPath()!)).ToList()
+ );
+
+ var successfulFiles = new List();
+
+ foreach (var localImage in images)
+ {
+ var imageFile = new FilePath(localImage.AbsolutePath);
+
+ // Write a txt with the same name as the image
+ var txtFile = imageFile.WithName(imageFile.NameWithoutExtension + ".txt");
+
+ // Read metadata
+ if (localImage.GenerationParameters?.PositivePrompt is { } positivePrompt)
+ {
+ await File.WriteAllTextAsync(txtFile, positivePrompt);
+
+ successfulFiles.Add(localImage);
+ }
+ }
+
+ notificationService.Show(
+ "Extracted prompts",
+ $"Extracted prompts from {successfulFiles.Count}/{images.Count} images.",
+ NotificationType.Success
+ );
+ }
+
#endregion
#region Info Section
diff --git a/StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs
new file mode 100644
index 00000000..3db3dabb
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls;
+using CommunityToolkit.Mvvm.ComponentModel;
+using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.Views;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.ViewModels;
+
+[View(typeof(WorkflowsPage))]
+[Singleton]
+public partial class WorkflowsPageViewModel : PageViewModelBase
+{
+ public override string Title => Resources.Label_Workflows;
+ public override IconSource IconSource => new FASymbolIconSource { Symbol = "fa-solid fa-circle-nodes" };
+
+ public IReadOnlyList Pages { get; }
+
+ [ObservableProperty]
+ private TabItem? selectedPage;
+
+ ///
+ public WorkflowsPageViewModel(
+ OpenArtBrowserViewModel openArtBrowserViewModel,
+ InstalledWorkflowsViewModel installedWorkflowsViewModel
+ )
+ {
+ Pages = new List(
+ new List([openArtBrowserViewModel, installedWorkflowsViewModel]).Select(
+ vm => new TabItem { Header = vm.Header, Content = vm }
+ )
+ );
+ SelectedPage = Pages.FirstOrDefault();
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
index c2831986..facb1298 100644
--- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
@@ -13,6 +13,8 @@
xmlns:api="clr-namespace:StabilityMatrix.Core.Models.Api;assembly=StabilityMatrix.Core"
xmlns:generic="clr-namespace:System.Collections.Generic;assembly=System.Collections"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
+ xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
+ xmlns:vendorLabs="clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs"
d:DataContext="{x:Static mocks:DesignData.CheckpointsPageViewModel}"
x:CompileBindings="True"
x:DataType="vm:CheckpointsPageViewModel"
@@ -138,7 +140,7 @@
TextWrapping="WrapWithOverflow"
IsVisible="{Binding IsConnectedModel}" />
-
-
+
-
@@ -60,7 +61,7 @@
@@ -103,7 +104,7 @@
CommandParameter="{Binding CivitModel}"
IsEnabled="{Binding !IsImporting}">
- -->
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs
new file mode 100644
index 00000000..5123da7d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs
@@ -0,0 +1,54 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using AvaloniaEdit;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Helpers;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+
+namespace StabilityMatrix.Avalonia.Views;
+
+[Transient]
+public partial class ConsoleOutputPage : UserControlBase
+{
+ private const int LineOffset = 5;
+
+ public ConsoleOutputPage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ TextEditorConfigs.Configure(Console, TextEditorPreset.Console);
+ }
+
+ protected override void OnUnloaded(RoutedEventArgs e)
+ {
+ base.OnUnloaded(e);
+ EventManager.Instance.ScrollToBottomRequested -= OnScrollToBottomRequested;
+ }
+
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+ EventManager.Instance.ScrollToBottomRequested += OnScrollToBottomRequested;
+ }
+
+ private void OnScrollToBottomRequested(object? sender, EventArgs e)
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ var editor = this.FindControl("Console");
+ if (editor?.Document == null)
+ return;
+ var line = Math.Max(editor.Document.LineCount - LineOffset, 1);
+ editor.ScrollToLine(line);
+ });
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
index ee304641..ac45af36 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
@@ -128,7 +128,7 @@
PlacementMargin="16,0,16,0"
TailVisibility="Collapsed"
Target="{Binding #InfoButton}">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml.cs
new file mode 100644
index 00000000..e0ed1d00
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml.cs
@@ -0,0 +1,13 @@
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.Views.Dialogs;
+
+[Transient]
+public partial class OpenArtWorkflowDialog : UserControlBase
+{
+ public OpenArtWorkflowDialog()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
new file mode 100644
index 00000000..35c104b5
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
@@ -0,0 +1,255 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs
new file mode 100644
index 00000000..191916d2
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs
@@ -0,0 +1,13 @@
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.Views;
+
+[Singleton]
+public partial class InstalledWorkflowsPage : UserControlBase
+{
+ public InstalledWorkflowsPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml b/StabilityMatrix.Avalonia/Views/MainWindow.axaml
index 75613afc..306a1a37 100644
--- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml
+++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml
@@ -70,11 +70,13 @@
Grid.RowSpan="2"
Name="NavigationView"
ItemInvoked="NavigationView_OnItemInvoked"
+ BackRequested="NavigationView_OnBackRequested"
PaneDisplayMode="Left"
IsPaneOpen="False"
OpenPaneLength="{Binding PaneWidth}"
IsSettingsVisible="False"
- IsBackEnabled="False"
+ IsBackEnabled="True"
+ IsBackButtonVisible="True"
MenuItemsSource="{Binding Pages, Mode=OneWay}"
FooterMenuItemsSource="{Binding FooterPages, Mode=OneWay}"
SelectedItem="{Binding SelectedCategory}">
diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
index 614754a6..eba477f8 100644
--- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
+++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
+using AsyncAwaitBestPractices;
using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls;
@@ -193,13 +194,52 @@ public partial class MainWindow : AppWindowBase
protected override void OnClosing(WindowClosingEventArgs e)
{
// Show confirmation if package running
- var launchPageViewModel = App.Services.GetRequiredService();
-
- launchPageViewModel.OnMainWindowClosing(e);
+ var runningPackageService = App.Services.GetRequiredService();
+ if (
+ runningPackageService.RunningPackages.Count > 0
+ && e.CloseReason is WindowCloseReason.WindowClosing
+ )
+ {
+ e.Cancel = true;
+
+ var dialog = CreateExitConfirmDialog();
+ Dispatcher
+ .UIThread.InvokeAsync(async () =>
+ {
+ if (
+ (TaskDialogStandardResult)await dialog.ShowAsync(true) == TaskDialogStandardResult.Yes
+ )
+ {
+ App.Services.GetRequiredService().Hide();
+ App.Shutdown();
+ }
+ })
+ .SafeFireAndForget();
+ }
base.OnClosing(e);
}
+ private static TaskDialog CreateExitConfirmDialog()
+ {
+ var dialog = DialogHelper.CreateTaskDialog(
+ Languages.Resources.Label_ConfirmExit,
+ Languages.Resources.Label_ConfirmExitDetail
+ );
+
+ dialog.ShowProgressBar = false;
+ dialog.FooterVisibility = TaskDialogFooterVisibility.Never;
+
+ dialog.Buttons = new List
+ {
+ new("Exit", TaskDialogStandardResult.Yes),
+ TaskDialogButton.CancelButton
+ };
+ dialog.Buttons[0].IsDefault = true;
+
+ return dialog;
+ }
+
///
protected override void OnClosed(EventArgs e)
{
@@ -406,4 +446,9 @@ public partial class MainWindow : AppWindowBase
e.Handled = true;
navigationService.GoBack();
}
+
+ private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e)
+ {
+ navigationService.GoBack();
+ }
}
diff --git a/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
new file mode 100644
index 00000000..c4f8189f
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs
new file mode 100644
index 00000000..b6a615f3
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs
@@ -0,0 +1,41 @@
+using System;
+using AsyncAwaitBestPractices;
+using Avalonia.Controls;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Models;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Avalonia.Views;
+
+[Singleton]
+public partial class OpenArtBrowserPage : UserControlBase
+{
+ private readonly ISettingsManager settingsManager;
+
+ public OpenArtBrowserPage(ISettingsManager settingsManager)
+ {
+ this.settingsManager = settingsManager;
+ InitializeComponent();
+ }
+
+ private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)
+ {
+ if (sender is not ScrollViewer scrollViewer)
+ return;
+
+ if (scrollViewer.Offset.Y == 0)
+ return;
+
+ var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f;
+
+ if (
+ isAtEnd
+ && settingsManager.Settings.IsWorkflowInfiniteScrollEnabled
+ && DataContext is IInfinitelyScroll scroll
+ )
+ {
+ scroll.LoadNextPageAsync().SafeFireAndForget();
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml
index 3b1a02bb..5b231271 100644
--- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml
@@ -2,15 +2,12 @@
x:Class="StabilityMatrix.Avalonia.Views.OutputsPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
- xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fluentAvalonia="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
- xmlns:models1="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:outputsPage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.OutputsPage"
xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll"
xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard"
@@ -23,13 +20,33 @@
x:DataType="vm:OutputsPageViewModel"
Focusable="True"
mc:Ignorable="d">
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
-
+
+
+
-
-
-
+ IsEnabled="{Binding !IsLoading}"
+ Label="{x:Static lang:Resources.Action_Refresh}">
+
+
+
+
+
+
@@ -160,11 +149,22 @@
+
+
-
+
@@ -206,7 +206,8 @@
IconSource="Delete"
Text="{x:Static lang:Resources.Action_Delete}" />
-
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml b/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml
index 91b2e671..594b03eb 100644
--- a/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml
+++ b/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml
@@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
@@ -10,8 +9,8 @@
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:database="clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core"
- xmlns:extensions="clr-namespace:StabilityMatrix.Core.Models.Packages.Extensions;assembly=StabilityMatrix.Core"
xmlns:packageManager="clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager"
+ xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="packageManager:PackageInstallDetailViewModel"
x:CompileBindings="True"
@@ -21,19 +20,21 @@
+ Margin="16, 16, 8, 8"
+ CornerRadius="8"
+ Source="{Binding SelectedPackage.PreviewImageUri}">
+
+
-
+
+ Content="{Binding SelectedPackage.LicenseType}" />
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -26,185 +36,32 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
@@ -215,99 +72,341 @@
TextWrapping="Wrap"
Text="{x:Static lang:Resources.Label_UnknownPackage}" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+ IsVisible="{Binding IsProgressVisible}"
+ RowDefinitions="Auto, *">
+
+
+ {
+ var target = this.FindDescendantOfType()
+ ?.GetVisualChildren()
+ .OfType()
+ .FirstOrDefault(x => x is { Name: "LaunchButton" });
+
+ if (target == null)
+ return;
+
+ var teachingTip = this.FindControl("LaunchTeachingTip");
+ if (teachingTip == null)
+ return;
+
+ teachingTip.Target = target;
+ teachingTip.IsOpen = true;
+ });
}
private void InitializeComponent()
diff --git a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
index 369c3f9a..5fb2d8a4 100644
--- a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
@@ -45,77 +45,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -204,40 +195,67 @@
-
-
-
+
+
+ Text="{x:Static lang:Resources.Label_WorkflowBrowser}" />
+ Margin="8,0,8,4"
+ Header="{x:Static lang:Resources.Label_InfiniteScrolling}">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+ Header="{x:Static lang:Resources.Label_Language}"
+ IconSource="Character">
-
+
-
+
+
diff --git a/StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml b/StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml
new file mode 100644
index 00000000..71d4982d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml.cs
new file mode 100644
index 00000000..443070e9
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml.cs
@@ -0,0 +1,13 @@
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.Views;
+
+[Singleton]
+public partial class WorkflowsPage : UserControlBase
+{
+ public WorkflowsPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/StabilityMatrix.Core/Api/IOpenArtApi.cs b/StabilityMatrix.Core/Api/IOpenArtApi.cs
new file mode 100644
index 00000000..f7e9ae19
--- /dev/null
+++ b/StabilityMatrix.Core/Api/IOpenArtApi.cs
@@ -0,0 +1,17 @@
+using Refit;
+using StabilityMatrix.Core.Models.Api.OpenArt;
+
+namespace StabilityMatrix.Core.Api;
+
+[Headers("User-Agent: StabilityMatrix")]
+public interface IOpenArtApi
+{
+ [Get("/feed")]
+ Task GetFeedAsync([Query] OpenArtFeedRequest request);
+
+ [Get("/list")]
+ Task SearchAsync([Query] OpenArtSearchRequest request);
+
+ [Post("/download")]
+ Task DownloadWorkflowAsync([Body] OpenArtDownloadRequest request);
+}
diff --git a/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs b/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs
index f496abc4..44d019fd 100644
--- a/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs
+++ b/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs
@@ -1,4 +1,5 @@
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Models.Packages.Extensions;
namespace StabilityMatrix.Core.Attributes;
@@ -11,4 +12,9 @@ public class TypedNodeOptionsAttribute : Attribute
public string? Name { get; init; }
public string[]? RequiredExtensions { get; init; }
+
+ public IEnumerable GetRequiredExtensionSpecifiers()
+ {
+ return RequiredExtensions?.Select(ExtensionSpecifier.Parse) ?? Enumerable.Empty();
+ }
}
diff --git a/StabilityMatrix.Core/Extensions/NullableExtensions.cs b/StabilityMatrix.Core/Extensions/NullableExtensions.cs
index 64264ef6..adf67100 100644
--- a/StabilityMatrix.Core/Extensions/NullableExtensions.cs
+++ b/StabilityMatrix.Core/Extensions/NullableExtensions.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
namespace StabilityMatrix.Core.Extensions;
@@ -16,7 +16,12 @@ public static class NullableExtensions
[DebuggerStepThrough]
[EditorBrowsable(EditorBrowsableState.Never)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static T Unwrap([NotNull] this T? obj, [CallerArgumentExpression("obj")] string? paramName = null)
+ [ContractAnnotation("obj:null => halt; obj:notnull => notnull")]
+ [return: System.Diagnostics.CodeAnalysis.NotNull]
+ public static T Unwrap(
+ [System.Diagnostics.CodeAnalysis.NotNull] this T? obj,
+ [CallerArgumentExpression("obj")] string? paramName = null
+ )
where T : class
{
if (obj is null)
@@ -35,7 +40,11 @@ public static class NullableExtensions
[DebuggerStepThrough]
[EditorBrowsable(EditorBrowsableState.Never)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static T Unwrap([NotNull] this T? obj, [CallerArgumentExpression("obj")] string? paramName = null)
+ [ContractAnnotation("obj:null => halt")]
+ public static T Unwrap(
+ [System.Diagnostics.CodeAnalysis.NotNull] this T? obj,
+ [CallerArgumentExpression("obj")] string? paramName = null
+ )
where T : struct
{
if (obj is null)
diff --git a/StabilityMatrix.Core/Extensions/ProgressExtensions.cs b/StabilityMatrix.Core/Extensions/ProgressExtensions.cs
index bd344ad9..6a7d0e89 100644
--- a/StabilityMatrix.Core/Extensions/ProgressExtensions.cs
+++ b/StabilityMatrix.Core/Extensions/ProgressExtensions.cs
@@ -7,16 +7,19 @@ namespace StabilityMatrix.Core.Extensions;
public static class ProgressExtensions
{
[return: NotNullIfNotNull(nameof(progress))]
- public static Action? AsProcessOutputHandler(
- this IProgress? progress
- )
+ public static Action? AsProcessOutputHandler(this IProgress? progress)
{
return progress == null
? null
: output =>
{
progress.Report(
- new ProgressReport { IsIndeterminate = true, Message = output.Text }
+ new ProgressReport
+ {
+ IsIndeterminate = true,
+ Message = output.Text,
+ ProcessOutput = output
+ }
);
};
}
diff --git a/StabilityMatrix.Core/Helper/EventManager.cs b/StabilityMatrix.Core/Helper/EventManager.cs
index e763022a..8efe3696 100644
--- a/StabilityMatrix.Core/Helper/EventManager.cs
+++ b/StabilityMatrix.Core/Helper/EventManager.cs
@@ -23,6 +23,7 @@ public class EventManager
public event EventHandler? DevModeSettingChanged;
public event EventHandler? UpdateAvailable;
public event EventHandler? PackageLaunchRequested;
+ public event EventHandler? PackageRelaunchRequested;
public event EventHandler? ScrollToBottomRequested;
public event EventHandler? ProgressChanged;
public event EventHandler? RunningPackageStatusChanged;
@@ -41,9 +42,11 @@ public class EventManager
public event EventHandler? InferenceUpscaleRequested;
public event EventHandler? InferenceImageToImageRequested;
public event EventHandler? InferenceImageToVideoRequested;
+ public event EventHandler? InferenceQueueCustomPrompt;
public event EventHandler? NavigateAndFindCivitModelRequested;
public event EventHandler? DownloadsTeachingTipRequested;
public event EventHandler? RecommendedModelsDialogClosed;
+ public event EventHandler? WorkflowInstalled;
public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress);
@@ -92,6 +95,9 @@ public class EventManager
public void OnInferenceImageToVideoRequested(LocalImageFile imageFile) =>
InferenceImageToVideoRequested?.Invoke(this, imageFile);
+ public void OnInferenceQueueCustomPrompt(InferenceQueueCustomPromptEventArgs e) =>
+ InferenceQueueCustomPrompt?.Invoke(this, e);
+
public void OnNavigateAndFindCivitModelRequested(int modelId) =>
NavigateAndFindCivitModelRequested?.Invoke(this, modelId);
@@ -100,4 +106,9 @@ public class EventManager
public void OnRecommendedModelsDialogClosed() =>
RecommendedModelsDialogClosed?.Invoke(this, EventArgs.Empty);
+
+ public void OnPackageRelaunchRequested(InstalledPackage package) =>
+ PackageRelaunchRequested?.Invoke(this, package);
+
+ public void OnWorkflowInstalled() => WorkflowInstalled?.Invoke(this, EventArgs.Empty);
}
diff --git a/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs
index b088c3bf..ccd855f8 100644
--- a/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs
+++ b/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs
@@ -10,4 +10,5 @@ public interface IPackageFactory
BasePackage? this[string packageName] { get; }
PackagePair? GetPackagePair(InstalledPackage? installedPackage);
IEnumerable GetPackagesByType(PackageType packageType);
+ BasePackage GetNewBasePackage(InstalledPackage installedPackage);
}
diff --git a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
index a31bca2b..3e1ccee2 100644
--- a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
+++ b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
@@ -1,22 +1,94 @@
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Packages;
+using StabilityMatrix.Core.Python;
+using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Helper.Factory;
[Singleton(typeof(IPackageFactory))]
public class PackageFactory : IPackageFactory
{
+ private readonly IGithubApiCache githubApiCache;
+ private readonly ISettingsManager settingsManager;
+ private readonly IDownloadService downloadService;
+ private readonly IPrerequisiteHelper prerequisiteHelper;
+ private readonly IPyRunner pyRunner;
+
///
/// Mapping of package.Name to package
///
private readonly Dictionary basePackages;
- public PackageFactory(IEnumerable basePackages)
+ public PackageFactory(
+ IEnumerable basePackages,
+ IGithubApiCache githubApiCache,
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyRunner pyRunner
+ )
{
+ this.githubApiCache = githubApiCache;
+ this.settingsManager = settingsManager;
+ this.downloadService = downloadService;
+ this.prerequisiteHelper = prerequisiteHelper;
+ this.pyRunner = pyRunner;
this.basePackages = basePackages.ToDictionary(x => x.Name);
}
+ public BasePackage GetNewBasePackage(InstalledPackage installedPackage)
+ {
+ return installedPackage.PackageName switch
+ {
+ "ComfyUI" => new ComfyUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "Fooocus" => new Fooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "stable-diffusion-webui"
+ => new A3WebUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "Fooocus-ControlNet-SDXL"
+ => new FocusControlNet(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "Fooocus-MRE"
+ => new FooocusMre(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "InvokeAI" => new InvokeAI(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "kohya_ss"
+ => new KohyaSs(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyRunner
+ ),
+ "OneTrainer"
+ => new OneTrainer(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "RuinedFooocus"
+ => new RuinedFooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "stable-diffusion-webui-forge"
+ => new SDWebForge(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "stable-diffusion-webui-directml"
+ => new StableDiffusionDirectMl(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper
+ ),
+ "stable-diffusion-webui-ux"
+ => new StableDiffusionUx(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper
+ ),
+ "StableSwarmUI"
+ => new StableSwarm(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "automatic"
+ => new VladAutomatic(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ "voltaML-fast-stable-diffusion"
+ => new VoltaML(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
+ _ => throw new ArgumentOutOfRangeException(nameof(installedPackage))
+ };
+ }
+
public IEnumerable GetAllAvailablePackages()
{
return basePackages.Values.OrderBy(p => p.InstallerSortOrder).ThenBy(p => p.DisplayName);
diff --git a/StabilityMatrix.Core/Helper/RemoteModels.cs b/StabilityMatrix.Core/Helper/RemoteModels.cs
index 3f6a49b5..deed5fb1 100644
--- a/StabilityMatrix.Core/Helper/RemoteModels.cs
+++ b/StabilityMatrix.Core/Helper/RemoteModels.cs
@@ -167,8 +167,14 @@ public static class RemoteModels
)
};
+ public static HybridModelFile ControlNetReferenceOnlyModel { get; } =
+ HybridModelFile.FromRemote("@ReferenceOnly");
+
public static IReadOnlyList ControlNetModels { get; } =
- ControlNets.Select(HybridModelFile.FromDownloadable).ToImmutableArray();
+ ControlNets
+ .Select(HybridModelFile.FromDownloadable)
+ .Concat([ControlNetReferenceOnlyModel])
+ .ToImmutableArray();
private static IEnumerable PromptExpansions =>
[
diff --git a/StabilityMatrix.Core/Helper/Utilities.cs b/StabilityMatrix.Core/Helper/Utilities.cs
index 5e8bfc2e..0aaf838d 100644
--- a/StabilityMatrix.Core/Helper/Utilities.cs
+++ b/StabilityMatrix.Core/Helper/Utilities.cs
@@ -1,8 +1,9 @@
using System.Reflection;
+using System.Text.RegularExpressions;
namespace StabilityMatrix.Core.Helper;
-public static class Utilities
+public static partial class Utilities
{
public static string GetAppVersion()
{
@@ -63,4 +64,24 @@ public static class Utilities
return stream;
}
+
+ public static string RemoveHtml(string? stringWithHtml)
+ {
+ var pruned =
+ stringWithHtml
+ ?.Replace(" ", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace(" ", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace("", $"{Environment.NewLine}{Environment.NewLine}")
+ .Replace("", $"{Environment.NewLine}{Environment.NewLine}") ?? string.Empty;
+ pruned = HtmlRegex().Replace(pruned, string.Empty);
+ return pruned;
+ }
+
+ [GeneratedRegex("<[^>]+>")]
+ private static partial Regex HtmlRegex();
}
diff --git a/StabilityMatrix.Core/Inference/ComfyClient.cs b/StabilityMatrix.Core/Inference/ComfyClient.cs
index 72ae9a27..3f3b894e 100644
--- a/StabilityMatrix.Core/Inference/ComfyClient.cs
+++ b/StabilityMatrix.Core/Inference/ComfyClient.cs
@@ -460,6 +460,22 @@ public class ComfyClient : InferenceClientBase
return info.Input.GetRequiredValueAsNestedList(optionName);
}
+ ///
+ /// Get a list of strings representing available optional options of a given node
+ ///
+ public async Task?> GetOptionalNodeOptionNamesAsync(
+ string nodeName,
+ string optionName,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var response = await comfyApi.GetObjectInfo(nodeName, cancellationToken).ConfigureAwait(false);
+
+ var info = response.GetValueOrDefault(nodeName);
+
+ return info?.Input.GetOptionalValueAsNestedList(optionName);
+ }
+
protected override void Dispose(bool disposing)
{
if (isDisposed)
diff --git a/StabilityMatrix.Core/Models/Api/CivitImage.cs b/StabilityMatrix.Core/Models/Api/CivitImage.cs
index 82aadce2..c29e3bac 100644
--- a/StabilityMatrix.Core/Models/Api/CivitImage.cs
+++ b/StabilityMatrix.Core/Models/Api/CivitImage.cs
@@ -19,5 +19,8 @@ public class CivitImage
[JsonPropertyName("hash")]
public string Hash { get; set; }
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
// TODO: "meta" ( object? )
}
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/ComfyAuxPreprocessor.cs b/StabilityMatrix.Core/Models/Api/Comfy/ComfyAuxPreprocessor.cs
new file mode 100644
index 00000000..0b93427f
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/Comfy/ComfyAuxPreprocessor.cs
@@ -0,0 +1,124 @@
+using System.Diagnostics.CodeAnalysis;
+using JetBrains.Annotations;
+
+namespace StabilityMatrix.Core.Models.Api.Comfy;
+
+///
+/// Collection of preprocessors included in
+///
+///
+[PublicAPI]
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+public record ComfyAuxPreprocessor(string Value) : StringValue(Value)
+{
+ public static ComfyAuxPreprocessor None { get; } = new("none");
+ public static ComfyAuxPreprocessor AnimeFaceSemSegPreprocessor { get; } =
+ new("AnimeFace_SemSegPreprocessor");
+ public static ComfyAuxPreprocessor BinaryPreprocessor { get; } = new("BinaryPreprocessor");
+ public static ComfyAuxPreprocessor CannyEdgePreprocessor { get; } = new("CannyEdgePreprocessor");
+ public static ComfyAuxPreprocessor ColorPreprocessor { get; } = new("ColorPreprocessor");
+ public static ComfyAuxPreprocessor DensePosePreprocessor { get; } = new("DensePosePreprocessor");
+ public static ComfyAuxPreprocessor DepthAnythingPreprocessor { get; } = new("DepthAnythingPreprocessor");
+ public static ComfyAuxPreprocessor ZoeDepthAnythingPreprocessor { get; } =
+ new("Zoe_DepthAnythingPreprocessor");
+ public static ComfyAuxPreprocessor DiffusionEdgePreprocessor { get; } = new("DiffusionEdge_Preprocessor");
+ public static ComfyAuxPreprocessor DWPreprocessor { get; } = new("DWPreprocessor");
+ public static ComfyAuxPreprocessor AnimalPosePreprocessor { get; } = new("AnimalPosePreprocessor");
+ public static ComfyAuxPreprocessor HEDPreprocessor { get; } = new("HEDPreprocessor");
+ public static ComfyAuxPreprocessor FakeScribblePreprocessor { get; } = new("FakeScribblePreprocessor");
+ public static ComfyAuxPreprocessor LeReSDepthMapPreprocessor { get; } = new("LeReS-DepthMapPreprocessor");
+ public static ComfyAuxPreprocessor LineArtPreprocessor { get; } = new("LineArtPreprocessor");
+ public static ComfyAuxPreprocessor AnimeLineArtPreprocessor { get; } = new("AnimeLineArtPreprocessor");
+ public static ComfyAuxPreprocessor LineartStandardPreprocessor { get; } =
+ new("LineartStandardPreprocessor");
+ public static ComfyAuxPreprocessor Manga2AnimeLineArtPreprocessor { get; } =
+ new("Manga2Anime_LineArt_Preprocessor");
+ public static ComfyAuxPreprocessor MediaPipeFaceMeshPreprocessor { get; } =
+ new("MediaPipe-FaceMeshPreprocessor");
+ public static ComfyAuxPreprocessor MeshGraphormerDepthMapPreprocessor { get; } =
+ new("MeshGraphormer-DepthMapPreprocessor");
+ public static ComfyAuxPreprocessor MiDaSNormalMapPreprocessor { get; } =
+ new("MiDaS-NormalMapPreprocessor");
+ public static ComfyAuxPreprocessor MiDaSDepthMapPreprocessor { get; } = new("MiDaS-DepthMapPreprocessor");
+ public static ComfyAuxPreprocessor MLSDPreprocessor { get; } = new("M-LSDPreprocessor");
+ public static ComfyAuxPreprocessor BAENormalMapPreprocessor { get; } = new("BAE-NormalMapPreprocessor");
+ public static ComfyAuxPreprocessor OneFormerCOCOSemSegPreprocessor { get; } =
+ new("OneFormer-COCO-SemSegPreprocessor");
+ public static ComfyAuxPreprocessor OneFormerADE20KSemSegPreprocessor { get; } =
+ new("OneFormer-ADE20K-SemSegPreprocessor");
+ public static ComfyAuxPreprocessor OpenposePreprocessor { get; } = new("OpenposePreprocessor");
+ public static ComfyAuxPreprocessor PiDiNetPreprocessor { get; } = new("PiDiNetPreprocessor");
+ public static ComfyAuxPreprocessor SavePoseKpsAsJsonFile { get; } = new("SavePoseKpsAsJsonFile");
+ public static ComfyAuxPreprocessor FacialPartColoringFromPoseKps { get; } =
+ new("FacialPartColoringFromPoseKps");
+ public static ComfyAuxPreprocessor ImageLuminanceDetector { get; } = new("ImageLuminanceDetector");
+ public static ComfyAuxPreprocessor ImageIntensityDetector { get; } = new("ImageIntensityDetector");
+ public static ComfyAuxPreprocessor ScribblePreprocessor { get; } = new("ScribblePreprocessor");
+ public static ComfyAuxPreprocessor ScribbleXDoGPreprocessor { get; } = new("Scribble_XDoG_Preprocessor");
+ public static ComfyAuxPreprocessor SAMPreprocessor { get; } = new("SAMPreprocessor");
+ public static ComfyAuxPreprocessor ShufflePreprocessor { get; } = new("ShufflePreprocessor");
+ public static ComfyAuxPreprocessor TEEDPreprocessor { get; } = new("TEEDPreprocessor");
+ public static ComfyAuxPreprocessor TilePreprocessor { get; } = new("TilePreprocessor");
+ public static ComfyAuxPreprocessor UniFormerSemSegPreprocessor { get; } =
+ new("UniFormer-SemSegPreprocessor");
+ public static ComfyAuxPreprocessor SemSegPreprocessor { get; } = new("SemSegPreprocessor");
+ public static ComfyAuxPreprocessor UnimatchOptFlowPreprocessor { get; } =
+ new("Unimatch_OptFlowPreprocessor");
+ public static ComfyAuxPreprocessor MaskOptFlow { get; } = new("MaskOptFlow");
+ public static ComfyAuxPreprocessor ZoeDepthMapPreprocessor { get; } = new("Zoe-DepthMapPreprocessor");
+
+ private static Dictionary DisplayNamesMapping { get; } =
+ new()
+ {
+ [None] = "None",
+ [AnimeFaceSemSegPreprocessor] = "Anime Face SemSeg Preprocessor",
+ [BinaryPreprocessor] = "Binary Preprocessor",
+ [CannyEdgePreprocessor] = "Canny Edge Preprocessor",
+ [ColorPreprocessor] = "Color Preprocessor",
+ [DensePosePreprocessor] = "DensePose Preprocessor",
+ [DepthAnythingPreprocessor] = "Depth Anything Preprocessor",
+ [ZoeDepthAnythingPreprocessor] = "Zoe Depth Anything Preprocessor",
+ [DiffusionEdgePreprocessor] = "Diffusion Edge Preprocessor",
+ [DWPreprocessor] = "DW Preprocessor",
+ [AnimalPosePreprocessor] = "Animal Pose Preprocessor",
+ [HEDPreprocessor] = "HED Preprocessor",
+ [FakeScribblePreprocessor] = "Fake Scribble Preprocessor",
+ [LeReSDepthMapPreprocessor] = "LeReS-DepthMap Preprocessor",
+ [LineArtPreprocessor] = "LineArt Preprocessor",
+ [AnimeLineArtPreprocessor] = "Anime LineArt Preprocessor",
+ [LineartStandardPreprocessor] = "Lineart Standard Preprocessor",
+ [Manga2AnimeLineArtPreprocessor] = "Manga2Anime LineArt Preprocessor",
+ [MediaPipeFaceMeshPreprocessor] = "MediaPipe FaceMesh Preprocessor",
+ [MeshGraphormerDepthMapPreprocessor] = "MeshGraphormer DepthMap Preprocessor",
+ [MiDaSNormalMapPreprocessor] = "MiDaS NormalMap Preprocessor",
+ [MiDaSDepthMapPreprocessor] = "MiDaS DepthMap Preprocessor",
+ [MLSDPreprocessor] = "M-LSD Preprocessor",
+ [BAENormalMapPreprocessor] = "BAE NormalMap Preprocessor",
+ [OneFormerCOCOSemSegPreprocessor] = "OneFormer COCO SemSeg Preprocessor",
+ [OneFormerADE20KSemSegPreprocessor] = "OneFormer ADE20K SemSeg Preprocessor",
+ [OpenposePreprocessor] = "Openpose Preprocessor",
+ [PiDiNetPreprocessor] = "PiDiNet Preprocessor",
+ [SavePoseKpsAsJsonFile] = "Save Pose Kps As Json File",
+ [FacialPartColoringFromPoseKps] = "Facial Part Coloring From Pose Kps",
+ [ImageLuminanceDetector] = "Image Luminance Detector",
+ [ImageIntensityDetector] = "Image Intensity Detector",
+ [ScribblePreprocessor] = "Scribble Preprocessor",
+ [ScribbleXDoGPreprocessor] = "Scribble XDoG Preprocessor",
+ [SAMPreprocessor] = "SAM Preprocessor",
+ [ShufflePreprocessor] = "Shuffle Preprocessor",
+ [TEEDPreprocessor] = "TEED Preprocessor",
+ [TilePreprocessor] = "Tile Preprocessor",
+ [UniFormerSemSegPreprocessor] = "UniFormer SemSeg Preprocessor",
+ [SemSegPreprocessor] = "SemSeg Preprocessor",
+ [UnimatchOptFlowPreprocessor] = "Unimatch OptFlow Preprocessor",
+ [MaskOptFlow] = "Mask OptFlow",
+ [ZoeDepthMapPreprocessor] = "Zoe DepthMap Preprocessor"
+ };
+
+ public static IEnumerable Defaults => DisplayNamesMapping.Keys;
+
+ public string DisplayName => DisplayNamesMapping.GetValueOrDefault(this, Value);
+
+ ///
+ public override string ToString() => Value;
+}
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs b/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs
index ecd04374..60072ac4 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs
@@ -9,13 +9,24 @@ public class ComfyInputInfo
[JsonPropertyName("required")]
public Dictionary? Required { get; set; }
+ [JsonPropertyName("optional")]
+ public Dictionary? Optional { get; set; }
+
public List? GetRequiredValueAsNestedList(string key)
{
var value = Required?[key];
- if (value is null) return null;
- var nested = value.Deserialize>>();
-
+ var nested = value?.Deserialize>>();
+
return nested?.SelectMany(x => x).ToList();
}
+
+ public List? GetOptionalValueAsNestedList(string key)
+ {
+ var value = Optional?[key];
+
+ var nested = value?.Deserialize()?[0].Deserialize>();
+
+ return nested;
+ }
}
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs
index abb66596..5257b5b2 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs
@@ -5,6 +5,15 @@
///
public record ModelConnections(string Name)
{
+ public ModelConnections(ModelConnections other)
+ {
+ Name = other.Name;
+ Model = other.Model;
+ VAE = other.VAE;
+ Clip = other.Clip;
+ Conditioning = other.Conditioning;
+ }
+
public ModelNodeConnection? Model { get; set; }
public VAENodeConnection? VAE { get; set; }
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
index 286dff0b..df14284a 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
@@ -7,6 +7,7 @@ using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
using StabilityMatrix.Core.Models.Database;
+using StabilityMatrix.Core.Models.Inference;
namespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;
@@ -131,23 +132,35 @@ public class ComfyNodeBuilder
public int StopAtClipLayer { get; init; } = -1;
}
- public static NamedComfyNode LatentFromBatch(
- string name,
- LatentNodeConnection samples,
- int batchIndex,
- int length
- )
+ public record LatentFromBatch : ComfyTypedNodeBase
{
- return new NamedComfyNode(name)
- {
- ClassType = "LatentFromBatch",
- Inputs = new Dictionary
- {
- ["samples"] = samples.Data,
- ["batch_index"] = batchIndex,
- ["length"] = length,
- }
- };
+ public required LatentNodeConnection Samples { get; init; }
+
+ [Range(0, 63)]
+ public int BatchIndex { get; init; } = 0;
+
+ [Range(1, 64)]
+ public int Length { get; init; } = 1;
+ }
+
+ public record LatentBlend : ComfyTypedNodeBase
+ {
+ public required LatentNodeConnection Samples1 { get; init; }
+
+ public required LatentNodeConnection Samples2 { get; init; }
+
+ [Range(0d, 1d)]
+ public double BlendFactor { get; init; } = 0.5;
+ }
+
+ public record ModelMergeSimple : ComfyTypedNodeBase
+ {
+ public required ModelNodeConnection Model1 { get; init; }
+
+ public required ModelNodeConnection Model2 { get; init; }
+
+ [Range(0d, 1d)]
+ public double Ratio { get; init; } = 1;
}
public static NamedComfyNode ImageUpscaleWithModel(
@@ -332,7 +345,7 @@ public class ComfyNodeBuilder
[TypedNodeOptions(
Name = "Inference_Core_PromptExpansion",
- RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes"]
+ RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.2.0"]
)]
public record PromptExpansion : ComfyTypedNodeBase
{
@@ -342,6 +355,73 @@ public class ComfyNodeBuilder
public bool LogPrompt { get; init; }
}
+ [TypedNodeOptions(
+ Name = "Inference_Core_AIO_Preprocessor",
+ RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.2.0"]
+ )]
+ public record AIOPreprocessor : ComfyTypedNodeBase
+ {
+ public required ImageNodeConnection Image { get; init; }
+
+ public required string Preprocessor { get; init; }
+
+ [Range(64, 2048)]
+ public int Resolution { get; init; } = 512;
+ }
+
+ [TypedNodeOptions(
+ Name = "Inference_Core_ReferenceOnlySimple",
+ RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.3.0"]
+ )]
+ public record ReferenceOnlySimple : ComfyTypedNodeBase
+ {
+ public required ModelNodeConnection Model { get; init; }
+
+ public required LatentNodeConnection Reference { get; init; }
+
+ [Range(1, 64)]
+ public int BatchSize { get; init; } = 1;
+ }
+
+ [TypedNodeOptions(
+ Name = "Inference_Core_LayeredDiffusionApply",
+ RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.4.0"]
+ )]
+ public record LayeredDiffusionApply : ComfyTypedNodeBase
+ {
+ public required ModelNodeConnection Model { get; init; }
+
+ ///
+ /// Available configs:
+ /// SD15, Attention Injection, attn_sharing
+ /// SDXL, Conv Injection
+ /// SDXL, Attention Injection
+ ///
+ public required string Config { get; init; }
+
+ [Range(-1d, 3d)]
+ public double Weight { get; init; } = 1.0;
+ }
+
+ [TypedNodeOptions(
+ Name = "Inference_Core_LayeredDiffusionDecodeRGBA",
+ RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.4.0"]
+ )]
+ public record LayeredDiffusionDecodeRgba : ComfyTypedNodeBase
+ {
+ public required LatentNodeConnection Samples { get; init; }
+
+ public required ImageNodeConnection Images { get; init; }
+
+ ///
+ /// Either "SD15" or "SDXL"
+ ///
+ public required string SdVersion { get; init; }
+
+ [Range(1, 4096)]
+ public int SubBatchSize { get; init; } = 16;
+ }
+
public ImageNodeConnection Lambda_LatentToImage(LatentNodeConnection latent, VAENodeConnection vae)
{
var name = GetUniqueName("VAEDecode");
@@ -818,7 +898,34 @@ public class ComfyNodeBuilder
public ModelConnections Base => Models["Base"];
public ModelConnections Refiner => Models["Refiner"];
- public PrimaryNodeConnection? Primary { get; set; }
+ public Dictionary SamplerTemporaryArgs { get; } = new();
+
+ public ModuleApplyStepTemporaryArgs? BaseSamplerTemporaryArgs
+ {
+ get => SamplerTemporaryArgs.GetValueOrDefault("Base");
+ set => SamplerTemporaryArgs["Base"] = value;
+ }
+
+ ///
+ /// The last primary set latent value, updated when is set to a latent value.
+ ///
+ public LatentNodeConnection? LastPrimaryLatent { get; private set; }
+
+ private PrimaryNodeConnection? primary;
+
+ public PrimaryNodeConnection? Primary
+ {
+ get => primary;
+ set
+ {
+ if (value?.IsT0 == true)
+ {
+ LastPrimaryLatent = value.AsT0;
+ }
+ primary = value;
+ }
+ }
+
public VAENodeConnection? PrimaryVAE { get; set; }
public Size PrimarySize { get; set; }
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs
index 5fbe4464..da088767 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs
@@ -1,10 +1,12 @@
using System.ComponentModel;
using System.Reflection;
using System.Text.Json.Serialization;
+using KGySoft.CoreLibraries;
using OneOf;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+using StabilityMatrix.Core.Models.Packages.Extensions;
namespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;
@@ -19,7 +21,10 @@ public class NodeDictionary : Dictionary
/// When inserting TypedNodes, this holds a mapping of ClassType to required extensions
///
[JsonIgnore]
- public Dictionary ClassTypeRequiredExtensions { get; } = new();
+ public Dictionary ClassTypeRequiredExtensions { get; } = new();
+
+ public IEnumerable RequiredExtensions =>
+ ClassTypeRequiredExtensions.Values.SelectMany(x => x);
///
/// Finds a unique node name given a base name, by appending _2, _3, etc.
@@ -63,7 +68,11 @@ public class NodeDictionary : Dictionary
{
if (options.RequiredExtensions != null)
{
- ClassTypeRequiredExtensions[namedNode.ClassType] = options.RequiredExtensions;
+ ClassTypeRequiredExtensions.AddOrUpdate(
+ namedNode.ClassType,
+ _ => options.GetRequiredExtensionSpecifiers().ToArray(),
+ (_, specifiers) => options.GetRequiredExtensionSpecifiers().Concat(specifiers).ToArray()
+ );
}
}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/NodesCount.cs b/StabilityMatrix.Core/Models/Api/OpenArt/NodesCount.cs
new file mode 100644
index 00000000..2509dcd7
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/NodesCount.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class NodesCount
+{
+ [JsonPropertyName("total")]
+ public long Total { get; set; }
+
+ [JsonPropertyName("primitive")]
+ public long Primitive { get; set; }
+
+ [JsonPropertyName("custom")]
+ public long Custom { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtCreator.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtCreator.cs
new file mode 100644
index 00000000..173d1cef
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtCreator.cs
@@ -0,0 +1,24 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtCreator
+{
+ [JsonPropertyName("uid")]
+ public string Uid { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("bio")]
+ public string Bio { get; set; }
+
+ [JsonPropertyName("avatar")]
+ public Uri Avatar { get; set; }
+
+ [JsonPropertyName("username")]
+ public string Username { get; set; }
+
+ [JsonPropertyName("dev_profile_url")]
+ public string DevProfileUrl { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDateTime.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDateTime.cs
new file mode 100644
index 00000000..5f45a232
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDateTime.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtDateTime
+{
+ [JsonPropertyName("_seconds")]
+ public long Seconds { get; set; }
+
+ public DateTimeOffset ToDateTimeOffset()
+ {
+ return DateTimeOffset.FromUnixTimeSeconds(Seconds);
+ }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadRequest.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadRequest.cs
new file mode 100644
index 00000000..cdf07d27
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadRequest.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+using Refit;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtDownloadRequest
+{
+ [AliasAs("workflow_id")]
+ [JsonPropertyName("workflow_id")]
+ public required string WorkflowId { get; set; }
+
+ [AliasAs("version_tag")]
+ [JsonPropertyName("version_tag")]
+ public string VersionTag { get; set; } = "latest";
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadResponse.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadResponse.cs
new file mode 100644
index 00000000..3cb61d79
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadResponse.cs
@@ -0,0 +1,12 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtDownloadResponse
+{
+ [JsonPropertyName("filename")]
+ public string Filename { get; set; }
+
+ [JsonPropertyName("payload")]
+ public string Payload { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtFeedRequest.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtFeedRequest.cs
new file mode 100644
index 00000000..f8dd5255
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtFeedRequest.cs
@@ -0,0 +1,21 @@
+using Refit;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+///
+/// Note that parameters Category, Custom Node and Sort should be used separately
+///
+public class OpenArtFeedRequest
+{
+ [AliasAs("category")]
+ public string Category { get; set; }
+
+ [AliasAs("sort")]
+ public string Sort { get; set; }
+
+ [AliasAs("custom_node")]
+ public string CustomNode { get; set; }
+
+ [AliasAs("cursor")]
+ public string Cursor { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchRequest.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchRequest.cs
new file mode 100644
index 00000000..27d944e3
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchRequest.cs
@@ -0,0 +1,18 @@
+using Refit;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtSearchRequest
+{
+ [AliasAs("keyword")]
+ public required string Keyword { get; set; }
+
+ [AliasAs("pageSize")]
+ public int PageSize { get; set; } = 30;
+
+ ///
+ /// 0-based index of the page to retrieve
+ ///
+ [AliasAs("currentPage")]
+ public int CurrentPage { get; set; } = 0;
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResponse.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResponse.cs
new file mode 100644
index 00000000..c58e308a
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResponse.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtSearchResponse
+{
+ [JsonPropertyName("items")]
+ public IEnumerable Items { get; set; }
+
+ [JsonPropertyName("total")]
+ public int Total { get; set; }
+
+ [JsonPropertyName("nextCursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResult.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResult.cs
new file mode 100644
index 00000000..a85b639c
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResult.cs
@@ -0,0 +1,33 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtSearchResult
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("creator")]
+ public OpenArtCreator Creator { get; set; }
+
+ [JsonPropertyName("stats")]
+ public OpenArtStats Stats { get; set; }
+
+ [JsonPropertyName("nodes_index")]
+ public IEnumerable NodesIndex { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+
+ [JsonPropertyName("categories")]
+ public IEnumerable Categories { get; set; }
+
+ [JsonPropertyName("thumbnails")]
+ public List Thumbnails { get; set; }
+
+ [JsonPropertyName("nodes_count")]
+ public NodesCount NodesCount { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtStats.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtStats.cs
new file mode 100644
index 00000000..4afa6118
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtStats.cs
@@ -0,0 +1,33 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtStats
+{
+ [JsonPropertyName("num_shares")]
+ public int NumShares { get; set; }
+
+ [JsonPropertyName("num_bookmarks")]
+ public int NumBookmarks { get; set; }
+
+ [JsonPropertyName("num_reviews")]
+ public int NumReviews { get; set; }
+
+ [JsonPropertyName("rating")]
+ public double Rating { get; set; }
+
+ [JsonPropertyName("num_comments")]
+ public int NumComments { get; set; }
+
+ [JsonPropertyName("num_likes")]
+ public int NumLikes { get; set; }
+
+ [JsonPropertyName("num_downloads")]
+ public int NumDownloads { get; set; }
+
+ [JsonPropertyName("num_runs")]
+ public int NumRuns { get; set; }
+
+ [JsonPropertyName("num_views")]
+ public int NumViews { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtThumbnail.cs b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtThumbnail.cs
new file mode 100644
index 00000000..05bfc22f
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/OpenArt/OpenArtThumbnail.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.OpenArt;
+
+public class OpenArtThumbnail
+{
+ [JsonPropertyName("width")]
+ public int Width { get; set; }
+
+ [JsonPropertyName("url")]
+ public Uri Url { get; set; }
+
+ [JsonPropertyName("height")]
+ public int Height { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/ComfyNodeMap.cs b/StabilityMatrix.Core/Models/ComfyNodeMap.cs
new file mode 100644
index 00000000..e6d024f7
--- /dev/null
+++ b/StabilityMatrix.Core/Models/ComfyNodeMap.cs
@@ -0,0 +1,8420 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace StabilityMatrix.Core.Models;
+
+public class ComfyNodeMap
+{
+ private static Dictionary lookup;
+
+ public static Dictionary Lookup
+ {
+ get => lookup ??= Deserialize();
+ }
+
+ public static Dictionary Deserialize()
+ {
+ var json = JsonSerializer.Deserialize>(Json);
+
+ return json!
+ .Select(
+ pair =>
+ (
+ Name: (pair.Value[1] as JsonObject)?["title_aux"]?.ToString() ?? string.Empty,
+ Url: pair.Key
+ )
+ )
+ .DistinctBy(x => x.Name)
+ .ToDictionary(pair => pair.Name, pair => pair.Url);
+ }
+
+ private const string Json = """
+ {
+ "https://gist.github.com/alkemann/7361b8eb966f29c8238fd323409efb68/raw/f9605be0b38d38d3e3a2988f89248ff557010076/alkemann.py": [
+ [
+ "Int to Text",
+ "Save A1 Image",
+ "Seed With Text"
+ ],
+ {
+ "title_aux": "alkemann nodes"
+ }
+ ],
+ "https://git.mmaker.moe/mmaker/sd-webui-color-enhance": [
+ [
+ "MMakerColorBlend",
+ "MMakerColorEnhance"
+ ],
+ {
+ "title_aux": "Color Enhance"
+ }
+ ],
+ "https://github.com/0xbitches/ComfyUI-LCM": [
+ [
+ "LCM_Sampler",
+ "LCM_Sampler_Advanced",
+ "LCM_img2img_Sampler",
+ "LCM_img2img_Sampler_Advanced"
+ ],
+ {
+ "title_aux": "Latent Consistency Model for ComfyUI"
+ }
+ ],
+ "https://github.com/1shadow1/hayo_comfyui_nodes/raw/main/LZCNodes.py": [
+ [
+ "LoadPILImages",
+ "MergeImages",
+ "make_transparentmask",
+ "tensor_trans_pil",
+ "words_generatee"
+ ],
+ {
+ "title_aux": "Hayo comfyui nodes"
+ }
+ ],
+ "https://github.com/42lux/ComfyUI-safety-checker": [
+ [
+ "Safety Checker"
+ ],
+ {
+ "title_aux": "ComfyUI-safety-checker"
+ }
+ ],
+ "https://github.com/54rt1n/ComfyUI-DareMerge": [
+ [
+ "DM_AdvancedDareModelMerger",
+ "DM_AdvancedModelMerger",
+ "DM_AttentionGradient",
+ "DM_BlockGradient",
+ "DM_BlockModelMerger",
+ "DM_DareClipMerger",
+ "DM_DareModelMergerBlock",
+ "DM_DareModelMergerElement",
+ "DM_DareModelMergerMBW",
+ "DM_GradientEdit",
+ "DM_GradientOperations",
+ "DM_GradientReporting",
+ "DM_InjectNoise",
+ "DM_LoRALoaderTags",
+ "DM_LoRAReporting",
+ "DM_MBWGradient",
+ "DM_MagnitudeMasker",
+ "DM_MaskEdit",
+ "DM_MaskOperations",
+ "DM_MaskReporting",
+ "DM_ModelReporting",
+ "DM_NormalizeModel",
+ "DM_QuadMasker",
+ "DM_ShellGradient",
+ "DM_SimpleMasker"
+ ],
+ {
+ "title_aux": "ComfyUI-DareMerge"
+ }
+ ],
+ "https://github.com/80sVectorz/ComfyUI-Static-Primitives": [
+ [
+ "FloatStaticPrimitive",
+ "IntStaticPrimitive",
+ "StringMlStaticPrimitive",
+ "StringStaticPrimitive"
+ ],
+ {
+ "title_aux": "ComfyUI-Static-Primitives"
+ }
+ ],
+ "https://github.com/AInseven/ComfyUI-fastblend": [
+ [
+ "FillDarkMask",
+ "InterpolateKeyFrame",
+ "MaskListcaptoBatch",
+ "MyOpenPoseNode",
+ "SmoothVideo",
+ "reBatchImage"
+ ],
+ {
+ "title_aux": "ComfyUI-fastblend"
+ }
+ ],
+ "https://github.com/AIrjen/OneButtonPrompt": [
+ [
+ "AutoNegativePrompt",
+ "CreatePromptVariant",
+ "OneButtonPreset",
+ "OneButtonPrompt",
+ "SavePromptToFile"
+ ],
+ {
+ "title_aux": "One Button Prompt"
+ }
+ ],
+ "https://github.com/AbdullahAlfaraj/Comfy-Photoshop-SD": [
+ [
+ "APS_LatentBatch",
+ "APS_Seed",
+ "ContentMaskLatent",
+ "ControlNetScript",
+ "ControlnetUnit",
+ "GaussianLatentImage",
+ "GetConfig",
+ "LoadImageBase64",
+ "LoadImageWithMetaData",
+ "LoadLorasFromPrompt",
+ "MaskExpansion"
+ ],
+ {
+ "title_aux": "Comfy-Photoshop-SD"
+ }
+ ],
+ "https://github.com/AbyssYuan0/ComfyUI_BadgerTools": [
+ [
+ "ApplyMaskToImage-badger",
+ "CropImageByMask-badger",
+ "ExpandImageWithColor-badger",
+ "FindThickLinesFromCanny-badger",
+ "FloatToInt-badger",
+ "FloatToString-badger",
+ "FrameToVideo-badger",
+ "GarbageCollect-badger",
+ "GetColorFromBorder-badger",
+ "GetDirName-badger",
+ "GetUUID-badger",
+ "IdentifyBorderColorToMask-badger",
+ "IdentifyColorToMask-badger",
+ "ImageNormalization-badger",
+ "ImageOverlap-badger",
+ "ImageScaleToSide-badger",
+ "IntToString-badger",
+ "SegmentToMaskByPoint-badger",
+ "StringToFizz-badger",
+ "TextListToString-badger",
+ "TrimTransparentEdges-badger",
+ "VideoCutFromDir-badger",
+ "VideoToFrame-badger",
+ "deleteDir-badger",
+ "findCenterOfMask-badger",
+ "getImageSide-badger",
+ "getParentDir-badger",
+ "mkdir-badger"
+ ],
+ {
+ "title_aux": "ComfyUI_BadgerTools"
+ }
+ ],
+ "https://github.com/Acly/comfyui-inpaint-nodes": [
+ [
+ "INPAINT_ApplyFooocusInpaint",
+ "INPAINT_InpaintWithModel",
+ "INPAINT_LoadFooocusInpaint",
+ "INPAINT_LoadInpaintModel",
+ "INPAINT_MaskedBlur",
+ "INPAINT_MaskedFill",
+ "INPAINT_VAEEncodeInpaintConditioning"
+ ],
+ {
+ "title_aux": "ComfyUI Inpaint Nodes"
+ }
+ ],
+ "https://github.com/Acly/comfyui-tooling-nodes": [
+ [
+ "ETN_ApplyMaskToImage",
+ "ETN_CropImage",
+ "ETN_LoadImageBase64",
+ "ETN_LoadMaskBase64",
+ "ETN_SendImageWebSocket"
+ ],
+ {
+ "title_aux": "ComfyUI Nodes for External Tooling"
+ }
+ ],
+ "https://github.com/Amorano/Jovimetrix": [
+ [],
+ {
+ "author": "amorano",
+ "description": "Webcams, GLSL shader, Media Streaming, Tick animation, Image manipulation,",
+ "nodename_pattern": " \\(jov\\)$",
+ "title": "Jovimetrix",
+ "title_aux": "Jovimetrix Composition Nodes"
+ }
+ ],
+ "https://github.com/ArtBot2023/CharacterFaceSwap": [
+ [
+ "Color Blend",
+ "Crop Face",
+ "Exclude Facial Feature",
+ "Generation Parameter Input",
+ "Generation Parameter Output",
+ "Image Full BBox",
+ "Load BiseNet",
+ "Load RetinaFace",
+ "Mask Contour",
+ "Segment Face",
+ "Uncrop Face"
+ ],
+ {
+ "title_aux": "Character Face Swap"
+ }
+ ],
+ "https://github.com/ArtVentureX/comfyui-animatediff": [
+ [
+ "AnimateDiffCombine",
+ "AnimateDiffLoraLoader",
+ "AnimateDiffModuleLoader",
+ "AnimateDiffSampler",
+ "AnimateDiffSlidingWindowOptions",
+ "ImageSizeAndBatchSize",
+ "LoadVideo"
+ ],
+ {
+ "title_aux": "AnimateDiff"
+ }
+ ],
+ "https://github.com/AustinMroz/ComfyUI-SpliceTools": [
+ [
+ "LogSigmas",
+ "RerangeSigmas",
+ "SpliceDenoised",
+ "SpliceLatents",
+ "TemporalSplice"
+ ],
+ {
+ "title_aux": "SpliceTools"
+ }
+ ],
+ "https://github.com/BadCafeCode/masquerade-nodes-comfyui": [
+ [
+ "Blur",
+ "Change Channel Count",
+ "Combine Masks",
+ "Constant Mask",
+ "Convert Color Space",
+ "Create QR Code",
+ "Create Rect Mask",
+ "Cut By Mask",
+ "Get Image Size",
+ "Image To Mask",
+ "Make Image Batch",
+ "Mask By Text",
+ "Mask Morphology",
+ "Mask To Region",
+ "MasqueradeIncrementer",
+ "Mix Color By Mask",
+ "Mix Images By Mask",
+ "Paste By Mask",
+ "Prune By Mask",
+ "Separate Mask Components",
+ "Unary Image Op",
+ "Unary Mask Op"
+ ],
+ {
+ "title_aux": "Masquerade Nodes"
+ }
+ ],
+ "https://github.com/Beinsezii/bsz-cui-extras": [
+ [
+ "BSZAbsoluteHires",
+ "BSZAspectHires",
+ "BSZColoredLatentImageXL",
+ "BSZCombinedHires",
+ "BSZHueChromaXL",
+ "BSZInjectionKSampler",
+ "BSZLatentDebug",
+ "BSZLatentFill",
+ "BSZLatentGradient",
+ "BSZLatentHSVAImage",
+ "BSZLatentOffsetXL",
+ "BSZLatentRGBAImage",
+ "BSZLatentbuster",
+ "BSZPixelbuster",
+ "BSZPixelbusterHelp",
+ "BSZPrincipledConditioning",
+ "BSZPrincipledSampler",
+ "BSZPrincipledScale",
+ "BSZStrangeResample"
+ ],
+ {
+ "title_aux": "bsz-cui-extras"
+ }
+ ],
+ "https://github.com/BennyKok/comfyui-deploy": [
+ [
+ "ComfyUIDeployExternalCheckpoint",
+ "ComfyUIDeployExternalImage",
+ "ComfyUIDeployExternalImageAlpha",
+ "ComfyUIDeployExternalLora",
+ "ComfyUIDeployExternalNumber",
+ "ComfyUIDeployExternalNumberInt",
+ "ComfyUIDeployExternalText"
+ ],
+ {
+ "author": "BennyKok",
+ "description": "",
+ "nickname": "Comfy Deploy",
+ "title": "comfyui-deploy",
+ "title_aux": "ComfyUI Deploy"
+ }
+ ],
+ "https://github.com/Bikecicle/ComfyUI-Waveform-Extensions/raw/main/EXT_AudioManipulation.py": [
+ [
+ "BatchJoinAudio",
+ "CutAudio",
+ "DuplicateAudio",
+ "JoinAudio",
+ "ResampleAudio",
+ "ReverseAudio",
+ "StretchAudio"
+ ],
+ {
+ "title_aux": "Waveform Extensions"
+ }
+ ],
+ "https://github.com/Billius-AI/ComfyUI-Path-Helper": [
+ [
+ "Add File Name Prefix",
+ "Add File Name Prefix Advanced",
+ "Add Folder",
+ "Add Folder Advanced",
+ "Create Project Root",
+ "Join Variables",
+ "Show Path",
+ "Show String"
+ ],
+ {
+ "title_aux": "ComfyUI-Path-Helper"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb": [
+ [
+ "BNK_AddCLIPSDXLParams",
+ "BNK_AddCLIPSDXLRParams",
+ "BNK_CLIPTextEncodeAdvanced",
+ "BNK_CLIPTextEncodeSDXLAdvanced"
+ ],
+ {
+ "title_aux": "Advanced CLIP Text Encode"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_Cutoff": [
+ [
+ "BNK_CutoffBasePrompt",
+ "BNK_CutoffRegionsToConditioning",
+ "BNK_CutoffRegionsToConditioning_ADV",
+ "BNK_CutoffSetRegions"
+ ],
+ {
+ "title_aux": "ComfyUI Cutoff"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_Noise": [
+ [
+ "BNK_DuplicateBatchIndex",
+ "BNK_GetSigma",
+ "BNK_InjectNoise",
+ "BNK_NoisyLatentImage",
+ "BNK_SlerpLatent",
+ "BNK_Unsampler"
+ ],
+ {
+ "title_aux": "ComfyUI Noise"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_SeeCoder": [
+ [
+ "ConcatConditioning",
+ "SEECoderImageEncode"
+ ],
+ {
+ "title_aux": "SeeCoder [WIP]"
+ }
+ ],
+ "https://github.com/BlenderNeko/ComfyUI_TiledKSampler": [
+ [
+ "BNK_TiledKSampler",
+ "BNK_TiledKSamplerAdvanced"
+ ],
+ {
+ "title_aux": "Tiled sampling for ComfyUI"
+ }
+ ],
+ "https://github.com/CYBERLOOM-INC/ComfyUI-nodes-hnmr": [
+ [
+ "CLIPIter",
+ "Dict2Model",
+ "GridImage",
+ "ImageBlend2",
+ "KSamplerOverrided",
+ "KSamplerSetting",
+ "KSamplerXYZ",
+ "LatentToHist",
+ "LatentToImage",
+ "ModelIter",
+ "RandomLatentImage",
+ "SaveStateDict",
+ "SaveText",
+ "StateDictLoader",
+ "StateDictMerger",
+ "StateDictMergerBlockWeighted",
+ "StateDictMergerBlockWeightedMulti",
+ "VAEDecodeBatched",
+ "VAEEncodeBatched",
+ "VAEIter"
+ ],
+ {
+ "title_aux": "ComfyUI-nodes-hnmr"
+ }
+ ],
+ "https://github.com/CaptainGrock/ComfyUIInvisibleWatermark/raw/main/Invisible%20Watermark.py": [
+ [
+ "Apply Invisible Watermark",
+ "Extract Watermark"
+ ],
+ {
+ "title_aux": "ComfyUIInvisibleWatermark"
+ }
+ ],
+ "https://github.com/Chan-0312/ComfyUI-IPAnimate": [
+ [
+ "IPAdapterAnimate"
+ ],
+ {
+ "title_aux": "ComfyUI-IPAnimate"
+ }
+ ],
+ "https://github.com/Chaoses-Ib/ComfyUI_Ib_CustomNodes": [
+ [
+ "ImageToPIL",
+ "LoadImageFromPath",
+ "PILToImage",
+ "PILToMask"
+ ],
+ {
+ "title_aux": "ComfyUI_Ib_CustomNodes"
+ }
+ ],
+ "https://github.com/Clybius/ComfyUI-Extra-Samplers": [
+ [
+ "SamplerCLYB_4M_SDE_Momentumized",
+ "SamplerCustomModelMixtureDuo",
+ "SamplerCustomNoise",
+ "SamplerCustomNoiseDuo",
+ "SamplerDPMPP_DualSDE_Momentumized",
+ "SamplerEulerAncestralDancing_Experimental",
+ "SamplerLCMCustom",
+ "SamplerRES_Momentumized",
+ "SamplerTTM"
+ ],
+ {
+ "title_aux": "ComfyUI Extra Samplers"
+ }
+ ],
+ "https://github.com/Clybius/ComfyUI-Latent-Modifiers": [
+ [
+ "Latent Diffusion Mega Modifier"
+ ],
+ {
+ "title_aux": "ComfyUI-Latent-Modifiers"
+ }
+ ],
+ "https://github.com/CosmicLaca/ComfyUI_Primere_Nodes": [
+ [
+ "PrimereAnyDetailer",
+ "PrimereAnyOutput",
+ "PrimereCKPT",
+ "PrimereCKPTLoader",
+ "PrimereCLIPEncoder",
+ "PrimereClearPrompt",
+ "PrimereDynamicParser",
+ "PrimereEmbedding",
+ "PrimereEmbeddingHandler",
+ "PrimereEmbeddingKeywordMerger",
+ "PrimereHypernetwork",
+ "PrimereImageSegments",
+ "PrimereKSampler",
+ "PrimereLCMSelector",
+ "PrimereLORA",
+ "PrimereLYCORIS",
+ "PrimereLatentNoise",
+ "PrimereLoraKeywordMerger",
+ "PrimereLoraStackMerger",
+ "PrimereLycorisKeywordMerger",
+ "PrimereLycorisStackMerger",
+ "PrimereMetaCollector",
+ "PrimereMetaRead",
+ "PrimereMetaSave",
+ "PrimereMidjourneyStyles",
+ "PrimereModelConceptSelector",
+ "PrimereModelKeyword",
+ "PrimereNetworkTagLoader",
+ "PrimerePrompt",
+ "PrimerePromptSwitch",
+ "PrimereRefinerPrompt",
+ "PrimereResolution",
+ "PrimereResolutionMultiplier",
+ "PrimereResolutionMultiplierMPX",
+ "PrimereSamplers",
+ "PrimereSamplersSteps",
+ "PrimereSeed",
+ "PrimereStepsCfg",
+ "PrimereStyleLoader",
+ "PrimereStylePile",
+ "PrimereTextOutput",
+ "PrimereVAE",
+ "PrimereVAELoader",
+ "PrimereVAESelector",
+ "PrimereVisualCKPT",
+ "PrimereVisualEmbedding",
+ "PrimereVisualHypernetwork",
+ "PrimereVisualLORA",
+ "PrimereVisualLYCORIS",
+ "PrimereVisualStyle"
+ ],
+ {
+ "title_aux": "Primere nodes for ComfyUI"
+ }
+ ],
+ "https://github.com/Danand/ComfyUI-ComfyCouple": [
+ [
+ "Attention couple",
+ "Comfy Couple"
+ ],
+ {
+ "author": "Rei D.",
+ "description": "If you want to draw two different characters together without blending their features, so you could try to check out this custom node.",
+ "nickname": "Danand",
+ "title": "Comfy Couple",
+ "title_aux": "ComfyUI-ComfyCouple"
+ }
+ ],
+ "https://github.com/Davemane42/ComfyUI_Dave_CustomNode": [
+ [
+ "ABGRemover",
+ "ConditioningStretch",
+ "ConditioningUpscale",
+ "MultiAreaConditioning",
+ "MultiLatentComposite"
+ ],
+ {
+ "title_aux": "Visual Area Conditioning / Latent composition"
+ }
+ ],
+ "https://github.com/Derfuu/Derfuu_ComfyUI_ModdedNodes": [
+ [
+ "ABSNode_DF",
+ "Absolute value",
+ "Ceil",
+ "CeilNode_DF",
+ "Conditioning area scale by ratio",
+ "ConditioningSetArea with tuples",
+ "ConditioningSetAreaEXT_DF",
+ "ConditioningSetArea_DF",
+ "CosNode_DF",
+ "Cosines",
+ "Divide",
+ "DivideNode_DF",
+ "EmptyLatentImage_DF",
+ "Float",
+ "Float debug print",
+ "Float2Tuple_DF",
+ "FloatDebugPrint_DF",
+ "FloatNode_DF",
+ "Floor",
+ "FloorNode_DF",
+ "Get image size",
+ "Get latent size",
+ "GetImageSize_DF",
+ "GetLatentSize_DF",
+ "Image scale by ratio",
+ "Image scale to side",
+ "ImageScale_Ratio_DF",
+ "ImageScale_Side_DF",
+ "Int debug print",
+ "Int to float",
+ "Int to tuple",
+ "Int2Float_DF",
+ "IntDebugPrint_DF",
+ "Integer",
+ "IntegerNode_DF",
+ "Latent Scale by ratio",
+ "Latent Scale to side",
+ "LatentComposite with tuples",
+ "LatentScale_Ratio_DF",
+ "LatentScale_Side_DF",
+ "MultilineStringNode_DF",
+ "Multiply",
+ "MultiplyNode_DF",
+ "PowNode_DF",
+ "Power",
+ "Random",
+ "RandomFloat_DF",
+ "SinNode_DF",
+ "Sinus",
+ "SqrtNode_DF",
+ "Square root",
+ "String debug print",
+ "StringNode_DF",
+ "Subtract",
+ "SubtractNode_DF",
+ "Sum",
+ "SumNode_DF",
+ "TanNode_DF",
+ "Tangent",
+ "Text",
+ "Text box",
+ "Tuple",
+ "Tuple debug print",
+ "Tuple multiply",
+ "Tuple swap",
+ "Tuple to floats",
+ "Tuple to ints",
+ "Tuple2Float_DF",
+ "TupleDebugPrint_DF",
+ "TupleNode_DF"
+ ],
+ {
+ "title_aux": "Derfuu_ComfyUI_ModdedNodes"
+ }
+ ],
+ "https://github.com/DonBaronFactory/ComfyUI-Cre8it-Nodes": [
+ [
+ "ApplySerialPrompter",
+ "ImageSizer",
+ "SerialPrompter"
+ ],
+ {
+ "author": "CRE8IT GmbH",
+ "description": "This extension offers various nodes.",
+ "nickname": "cre8Nodes",
+ "title": "cr8SerialPrompter",
+ "title_aux": "ComfyUI-Cre8it-Nodes"
+ }
+ ],
+ "https://github.com/Electrofried/ComfyUI-OpenAINode": [
+ [
+ "OpenAINode"
+ ],
+ {
+ "title_aux": "OpenAINode"
+ }
+ ],
+ "https://github.com/EllangoK/ComfyUI-post-processing-nodes": [
+ [
+ "ArithmeticBlend",
+ "AsciiArt",
+ "Blend",
+ "Blur",
+ "CannyEdgeMask",
+ "ChromaticAberration",
+ "ColorCorrect",
+ "ColorTint",
+ "Dissolve",
+ "Dither",
+ "DodgeAndBurn",
+ "FilmGrain",
+ "Glow",
+ "HSVThresholdMask",
+ "KMeansQuantize",
+ "KuwaharaBlur",
+ "Parabolize",
+ "PencilSketch",
+ "PixelSort",
+ "Pixelize",
+ "Quantize",
+ "Sharpen",
+ "SineWave",
+ "Solarize",
+ "Vignette"
+ ],
+ {
+ "title_aux": "ComfyUI-post-processing-nodes"
+ }
+ ],
+ "https://github.com/Extraltodeus/ComfyUI-AutomaticCFG": [
+ [
+ "Automatic CFG",
+ "Automatic CFG channels multipliers"
+ ],
+ {
+ "title_aux": "ComfyUI-AutomaticCFG"
+ }
+ ],
+ "https://github.com/Extraltodeus/LoadLoraWithTags": [
+ [
+ "LoraLoaderTagsQuery"
+ ],
+ {
+ "title_aux": "LoadLoraWithTags"
+ }
+ ],
+ "https://github.com/Extraltodeus/noise_latent_perlinpinpin": [
+ [
+ "NoisyLatentPerlin"
+ ],
+ {
+ "title_aux": "noise latent perlinpinpin"
+ }
+ ],
+ "https://github.com/Extraltodeus/sigmas_tools_and_the_golden_scheduler": [
+ [
+ "Get sigmas as float",
+ "Graph sigmas",
+ "Manual scheduler",
+ "Merge sigmas by average",
+ "Merge sigmas gradually",
+ "Multiply sigmas",
+ "Split and concatenate sigmas",
+ "The Golden Scheduler"
+ ],
+ {
+ "title_aux": "sigmas_tools_and_the_golden_scheduler"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-Frame-Interpolation": [
+ [
+ "AMT VFI",
+ "CAIN VFI",
+ "EISAI VFI",
+ "FILM VFI",
+ "FLAVR VFI",
+ "GMFSS Fortuna VFI",
+ "IFRNet VFI",
+ "IFUnet VFI",
+ "KSampler Gradually Adding More Denoise (efficient)",
+ "M2M VFI",
+ "Make Interpolation State List",
+ "RIFE VFI",
+ "STMFNet VFI",
+ "Sepconv VFI"
+ ],
+ {
+ "title_aux": "ComfyUI Frame Interpolation"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-Loopchain": [
+ [
+ "EmptyLatentImageLoop",
+ "FolderToImageStorage",
+ "ImageStorageExportLoop",
+ "ImageStorageImport",
+ "ImageStorageReset",
+ "LatentStorageExportLoop",
+ "LatentStorageImport",
+ "LatentStorageReset"
+ ],
+ {
+ "title_aux": "ComfyUI Loopchain"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-MotionDiff": [
+ [
+ "EmptyMotionData",
+ "ExportSMPLTo3DSoftware",
+ "MotionCLIPTextEncode",
+ "MotionDataVisualizer",
+ "MotionDiffLoader",
+ "MotionDiffSimpleSampler",
+ "RenderSMPLMesh",
+ "SMPLLoader",
+ "SaveSMPL",
+ "SmplifyMotionData"
+ ],
+ {
+ "title_aux": "ComfyUI MotionDiff"
+ }
+ ],
+ "https://github.com/Fannovel16/ComfyUI-Video-Matting": [
+ [
+ "BRIAAI Matting",
+ "Robust Video Matting"
+ ],
+ {
+ "title_aux": "ComfyUI-Video-Matting"
+ }
+ ],
+ "https://github.com/Fannovel16/comfyui_controlnet_aux": [
+ [
+ "AIO_Preprocessor",
+ "AnimalPosePreprocessor",
+ "AnimeFace_SemSegPreprocessor",
+ "AnimeLineArtPreprocessor",
+ "BAE-NormalMapPreprocessor",
+ "BinaryPreprocessor",
+ "CannyEdgePreprocessor",
+ "ColorPreprocessor",
+ "DWPreprocessor",
+ "DensePosePreprocessor",
+ "DepthAnythingPreprocessor",
+ "DiffusionEdge_Preprocessor",
+ "FacialPartColoringFromPoseKps",
+ "FakeScribblePreprocessor",
+ "HEDPreprocessor",
+ "HintImageEnchance",
+ "ImageGenResolutionFromImage",
+ "ImageGenResolutionFromLatent",
+ "ImageIntensityDetector",
+ "ImageLuminanceDetector",
+ "InpaintPreprocessor",
+ "LeReS-DepthMapPreprocessor",
+ "LineArtPreprocessor",
+ "LineartStandardPreprocessor",
+ "M-LSDPreprocessor",
+ "Manga2Anime_LineArt_Preprocessor",
+ "MaskOptFlow",
+ "MediaPipe-FaceMeshPreprocessor",
+ "MeshGraphormer-DepthMapPreprocessor",
+ "MiDaS-DepthMapPreprocessor",
+ "MiDaS-NormalMapPreprocessor",
+ "OneFormer-ADE20K-SemSegPreprocessor",
+ "OneFormer-COCO-SemSegPreprocessor",
+ "OpenposePreprocessor",
+ "PiDiNetPreprocessor",
+ "PixelPerfectResolution",
+ "SAMPreprocessor",
+ "SavePoseKpsAsJsonFile",
+ "ScribblePreprocessor",
+ "Scribble_XDoG_Preprocessor",
+ "SemSegPreprocessor",
+ "ShufflePreprocessor",
+ "TEEDPreprocessor",
+ "TilePreprocessor",
+ "UniFormer-SemSegPreprocessor",
+ "Unimatch_OptFlowPreprocessor",
+ "Zoe-DepthMapPreprocessor",
+ "Zoe_DepthAnythingPreprocessor"
+ ],
+ {
+ "author": "tstandley",
+ "title_aux": "ComfyUI's ControlNet Auxiliary Preprocessors"
+ }
+ ],
+ "https://github.com/Feidorian/feidorian-ComfyNodes": [
+ [],
+ {
+ "nodename_pattern": "^Feidorian_",
+ "title_aux": "feidorian-ComfyNodes"
+ }
+ ],
+ "https://github.com/Fictiverse/ComfyUI_Fictiverse": [
+ [
+ "Add Noise to Image with Mask",
+ "Color correction",
+ "Displace Image with Depth",
+ "Displace Images with Mask",
+ "Zoom Image with Depth"
+ ],
+ {
+ "title_aux": "ComfyUI Fictiverse Nodes"
+ }
+ ],
+ "https://github.com/FizzleDorf/ComfyUI-AIT": [
+ [
+ "AIT_Unet_Loader",
+ "AIT_VAE_Encode_Loader"
+ ],
+ {
+ "title_aux": "ComfyUI-AIT"
+ }
+ ],
+ "https://github.com/FizzleDorf/ComfyUI_FizzNodes": [
+ [
+ "AbsCosWave",
+ "AbsSinWave",
+ "BatchGLIGENSchedule",
+ "BatchPromptSchedule",
+ "BatchPromptScheduleEncodeSDXL",
+ "BatchPromptScheduleLatentInput",
+ "BatchPromptScheduleNodeFlowEnd",
+ "BatchPromptScheduleSDXLLatentInput",
+ "BatchStringSchedule",
+ "BatchValueSchedule",
+ "BatchValueScheduleLatentInput",
+ "CalculateFrameOffset",
+ "ConcatStringSingle",
+ "CosWave",
+ "FizzFrame",
+ "FizzFrameConcatenate",
+ "ImageBatchFromValueSchedule",
+ "Init FizzFrame",
+ "InvCosWave",
+ "InvSinWave",
+ "Lerp",
+ "PromptSchedule",
+ "PromptScheduleEncodeSDXL",
+ "PromptScheduleNodeFlow",
+ "PromptScheduleNodeFlowEnd",
+ "SawtoothWave",
+ "SinWave",
+ "SquareWave",
+ "StringConcatenate",
+ "StringSchedule",
+ "TriangleWave",
+ "ValueSchedule",
+ "convertKeyframeKeysToBatchKeys"
+ ],
+ {
+ "title_aux": "FizzNodes"
+ }
+ ],
+ "https://github.com/FlyingFireCo/tiled_ksampler": [
+ [
+ "Asymmetric Tiled KSampler",
+ "Circular VAEDecode",
+ "Tiled KSampler"
+ ],
+ {
+ "title_aux": "tiled_ksampler"
+ }
+ ],
+ "https://github.com/Franck-Demongin/NX_PromptStyler": [
+ [
+ "NX_PromptStyler"
+ ],
+ {
+ "title_aux": "NX_PromptStyler"
+ }
+ ],
+ "https://github.com/GMapeSplat/ComfyUI_ezXY": [
+ [
+ "ConcatenateString",
+ "ItemFromDropdown",
+ "IterationDriver",
+ "JoinImages",
+ "LineToConsole",
+ "NumberFromList",
+ "NumbersToList",
+ "PlotImages",
+ "StringFromList",
+ "StringToLabel",
+ "StringsToList",
+ "ezMath",
+ "ezXY_AssemblePlot",
+ "ezXY_Driver"
+ ],
+ {
+ "title_aux": "ezXY scripts and nodes"
+ }
+ ],
+ "https://github.com/GTSuya-Studio/ComfyUI-Gtsuya-Nodes": [
+ [
+ "Danbooru (ID)",
+ "Danbooru (Random)",
+ "Random File From Path",
+ "Replace Strings",
+ "Simple Wildcards",
+ "Simple Wildcards (Dir.)",
+ "Wildcards Nodes"
+ ],
+ {
+ "title_aux": "ComfyUI-GTSuya-Nodes"
+ }
+ ],
+ "https://github.com/Gourieff/comfyui-reactor-node": [
+ [
+ "ReActorFaceSwap",
+ "ReActorLoadFaceModel",
+ "ReActorRestoreFace",
+ "ReActorSaveFaceModel"
+ ],
+ {
+ "title_aux": "ReActor Node for ComfyUI"
+ }
+ ],
+ "https://github.com/HAL41/ComfyUI-aichemy-nodes": [
+ [
+ "aichemyYOLOv8Segmentation"
+ ],
+ {
+ "title_aux": "ComfyUI aichemy nodes"
+ }
+ ],
+ "https://github.com/Hangover3832/ComfyUI-Hangover-Moondream": [
+ [
+ "Moondream Interrogator (NO COMMERCIAL USE)"
+ ],
+ {
+ "title_aux": "ComfyUI-Hangover-Moondream"
+ }
+ ],
+ "https://github.com/Hangover3832/ComfyUI-Hangover-Nodes": [
+ [
+ "Image Scale Bounding Box",
+ "MS kosmos-2 Interrogator",
+ "Make Inpaint Model",
+ "Save Image w/o Metadata"
+ ],
+ {
+ "title_aux": "ComfyUI-Hangover-Nodes"
+ }
+ ],
+ "https://github.com/Haoming02/comfyui-diffusion-cg": [
+ [
+ "Normalization",
+ "NormalizationXL",
+ "Recenter",
+ "Recenter XL"
+ ],
+ {
+ "title_aux": "ComfyUI Diffusion Color Grading"
+ }
+ ],
+ "https://github.com/Haoming02/comfyui-floodgate": [
+ [
+ "FloodGate"
+ ],
+ {
+ "title_aux": "ComfyUI Floodgate"
+ }
+ ],
+ "https://github.com/HaydenReeve/ComfyUI-Better-Strings": [
+ [
+ "BetterString"
+ ],
+ {
+ "title_aux": "ComfyUI Better Strings"
+ }
+ ],
+ "https://github.com/HebelHuber/comfyui-enhanced-save-node": [
+ [
+ "EnhancedSaveNode"
+ ],
+ {
+ "title_aux": "comfyui-enhanced-save-node"
+ }
+ ],
+ "https://github.com/Hiero207/ComfyUI-Hiero-Nodes": [
+ [
+ "Post to Discord w/ Webhook"
+ ],
+ {
+ "author": "Hiero",
+ "description": "Just some nodes that I wanted/needed, so I made them.",
+ "nickname": "HNodes",
+ "title": "Hiero-Nodes",
+ "title_aux": "ComfyUI-Hiero-Nodes"
+ }
+ ],
+ "https://github.com/IDGallagher/ComfyUI-IG-Nodes": [
+ [
+ "IG Analyze SSIM",
+ "IG Cross Fade Images",
+ "IG Explorer",
+ "IG Float",
+ "IG Folder",
+ "IG Int",
+ "IG Load Image",
+ "IG Load Images",
+ "IG Multiply",
+ "IG Path Join",
+ "IG String",
+ "IG ZFill"
+ ],
+ {
+ "author": "IDGallagher",
+ "description": "Custom nodes to aid in the exploration of Latent Space",
+ "nickname": "IG Interpolation Nodes",
+ "title": "IG Interpolation Nodes",
+ "title_aux": "IG Interpolation Nodes"
+ }
+ ],
+ "https://github.com/Inzaniak/comfyui-ranbooru": [
+ [
+ "PromptBackground",
+ "PromptLimit",
+ "PromptMix",
+ "PromptRandomWeight",
+ "PromptRemove",
+ "Ranbooru",
+ "RanbooruURL",
+ "RandomPicturePath"
+ ],
+ {
+ "title_aux": "Ranbooru for ComfyUI"
+ }
+ ],
+ "https://github.com/JPS-GER/ComfyUI_JPS-Nodes": [
+ [
+ "Conditioning Switch (JPS)",
+ "ControlNet Switch (JPS)",
+ "Crop Image Pipe (JPS)",
+ "Crop Image Settings (JPS)",
+ "Crop Image Square (JPS)",
+ "Crop Image TargetSize (JPS)",
+ "CtrlNet CannyEdge Pipe (JPS)",
+ "CtrlNet CannyEdge Settings (JPS)",
+ "CtrlNet MiDaS Pipe (JPS)",
+ "CtrlNet MiDaS Settings (JPS)",
+ "CtrlNet OpenPose Pipe (JPS)",
+ "CtrlNet OpenPose Settings (JPS)",
+ "CtrlNet ZoeDepth Pipe (JPS)",
+ "CtrlNet ZoeDepth Settings (JPS)",
+ "Disable Enable Switch (JPS)",
+ "Enable Disable Switch (JPS)",
+ "Generation TXT IMG Settings (JPS)",
+ "Get Date Time String (JPS)",
+ "Get Image Size (JPS)",
+ "IP Adapter Settings (JPS)",
+ "IP Adapter Settings Pipe (JPS)",
+ "IP Adapter Single Settings (JPS)",
+ "IP Adapter Single Settings Pipe (JPS)",
+ "IPA Switch (JPS)",
+ "Image Switch (JPS)",
+ "ImageToImage Pipe (JPS)",
+ "ImageToImage Settings (JPS)",
+ "Images Masks MultiPipe (JPS)",
+ "Integer Switch (JPS)",
+ "Largest Int (JPS)",
+ "Latent Switch (JPS)",
+ "Lora Loader (JPS)",
+ "Mask Switch (JPS)",
+ "Model Switch (JPS)",
+ "Multiply Float Float (JPS)",
+ "Multiply Int Float (JPS)",
+ "Multiply Int Int (JPS)",
+ "Resolution Multiply (JPS)",
+ "Revision Settings (JPS)",
+ "Revision Settings Pipe (JPS)",
+ "SDXL Basic Settings (JPS)",
+ "SDXL Basic Settings Pipe (JPS)",
+ "SDXL Fundamentals MultiPipe (JPS)",
+ "SDXL Prompt Handling (JPS)",
+ "SDXL Prompt Handling Plus (JPS)",
+ "SDXL Prompt Styler (JPS)",
+ "SDXL Recommended Resolution Calc (JPS)",
+ "SDXL Resolutions (JPS)",
+ "Sampler Scheduler Settings (JPS)",
+ "Save Images Plus (JPS)",
+ "Substract Int Int (JPS)",
+ "Text Concatenate (JPS)",
+ "Text Prompt (JPS)",
+ "VAE Switch (JPS)"
+ ],
+ {
+ "author": "JPS",
+ "description": "Various nodes to handle SDXL Resolutions, SDXL Basic Settings, IP Adapter Settings, Revision Settings, SDXL Prompt Styler, Crop Image to Square, Crop Image to Target Size, Get Date-Time String, Resolution Multiply, Largest Integer, 5-to-1 Switches for Integer, Images, Latents, Conditioning, Model, VAE, ControlNet",
+ "nickname": "JPS Custom Nodes",
+ "title": "JPS Custom Nodes for ComfyUI",
+ "title_aux": "JPS Custom Nodes for ComfyUI"
+ }
+ ],
+ "https://github.com/JaredTherriault/ComfyUI-JNodes": [
+ [
+ "JNodes_AddOrSetMetaDataKey",
+ "JNodes_AnyToString",
+ "JNodes_AppendReversedFrames",
+ "JNodes_BooleanSelectorWithString",
+ "JNodes_CheckpointSelectorWithString",
+ "JNodes_GetOutputDirectory",
+ "JNodes_GetParameterFromList",
+ "JNodes_GetParameterGlobal",
+ "JNodes_GetTempDirectory",
+ "JNodes_ImageFormatSelector",
+ "JNodes_ImageSizeSelector",
+ "JNodes_LoadVideo",
+ "JNodes_LoraExtractor",
+ "JNodes_OutVideoInfo",
+ "JNodes_ParseDynamicPrompts",
+ "JNodes_ParseParametersToGlobalList",
+ "JNodes_ParseWildcards",
+ "JNodes_PromptBuilderSingleSubject",
+ "JNodes_RemoveCommentedText",
+ "JNodes_RemoveMetaDataKey",
+ "JNodes_RemoveParseableDataForInference",
+ "JNodes_SamplerSelectorWithString",
+ "JNodes_SaveImageWithOutput",
+ "JNodes_SaveVideo",
+ "JNodes_SchedulerSelectorWithString",
+ "JNodes_SearchAndReplace",
+ "JNodes_SearchAndReplaceFromFile",
+ "JNodes_SearchAndReplaceFromList",
+ "JNodes_SetNegativePromptInMetaData",
+ "JNodes_SetPositivePromptInMetaData",
+ "JNodes_SplitAndJoin",
+ "JNodes_StringLiteral",
+ "JNodes_SyncedStringLiteral",
+ "JNodes_TokenCounter",
+ "JNodes_TrimAndStrip",
+ "JNodes_UploadVideo",
+ "JNodes_VaeSelectorWithString"
+ ],
+ {
+ "title_aux": "ComfyUI-JNodes"
+ }
+ ],
+ "https://github.com/JcandZero/ComfyUI_GLM4Node": [
+ [
+ "GLM3_turbo_CHAT",
+ "GLM4_CHAT",
+ "GLM4_Vsion_IMGURL"
+ ],
+ {
+ "title_aux": "ComfyUI_GLM4Node"
+ }
+ ],
+ "https://github.com/Jcd1230/rembg-comfyui-node": [
+ [
+ "Image Remove Background (rembg)"
+ ],
+ {
+ "title_aux": "Rembg Background Removal Node for ComfyUI"
+ }
+ ],
+ "https://github.com/JerryOrbachJr/ComfyUI-RandomSize": [
+ [
+ "JOJR_RandomSize"
+ ],
+ {
+ "author": "JerryOrbachJr",
+ "description": "A ComfyUI custom node that randomly selects a height and width pair from a list in a config file",
+ "nickname": "Random Size",
+ "title": "Random Size",
+ "title_aux": "ComfyUI-RandomSize"
+ }
+ ],
+ "https://github.com/Jordach/comfy-plasma": [
+ [
+ "JDC_AutoContrast",
+ "JDC_BlendImages",
+ "JDC_BrownNoise",
+ "JDC_Contrast",
+ "JDC_EqualizeGrey",
+ "JDC_GaussianBlur",
+ "JDC_GreyNoise",
+ "JDC_Greyscale",
+ "JDC_ImageLoader",
+ "JDC_ImageLoaderMeta",
+ "JDC_PinkNoise",
+ "JDC_Plasma",
+ "JDC_PlasmaSampler",
+ "JDC_PowerImage",
+ "JDC_RandNoise",
+ "JDC_ResizeFactor"
+ ],
+ {
+ "title_aux": "comfy-plasma"
+ }
+ ],
+ "https://github.com/Kaharos94/ComfyUI-Saveaswebp": [
+ [
+ "Save_as_webp"
+ ],
+ {
+ "title_aux": "ComfyUI-Saveaswebp"
+ }
+ ],
+ "https://github.com/Kangkang625/ComfyUI-paint-by-example": [
+ [
+ "PaintbyExamplePipeLoader",
+ "PaintbyExampleSampler"
+ ],
+ {
+ "title_aux": "ComfyUI-Paint-by-Example"
+ }
+ ],
+ "https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet": [
+ [
+ "ACN_AdvancedControlNetApply",
+ "ACN_ControlNetLoaderWithLoraAdvanced",
+ "ACN_DefaultUniversalWeights",
+ "ACN_SparseCtrlIndexMethodNode",
+ "ACN_SparseCtrlLoaderAdvanced",
+ "ACN_SparseCtrlMergedLoaderAdvanced",
+ "ACN_SparseCtrlRGBPreprocessor",
+ "ACN_SparseCtrlSpreadMethodNode",
+ "ControlNetLoaderAdvanced",
+ "CustomControlNetWeights",
+ "CustomT2IAdapterWeights",
+ "DiffControlNetLoaderAdvanced",
+ "LatentKeyframe",
+ "LatentKeyframeBatchedGroup",
+ "LatentKeyframeGroup",
+ "LatentKeyframeTiming",
+ "LoadImagesFromDirectory",
+ "ScaledSoftControlNetWeights",
+ "ScaledSoftMaskedUniversalWeights",
+ "SoftControlNetWeights",
+ "SoftT2IAdapterWeights",
+ "TimestepKeyframe"
+ ],
+ {
+ "title_aux": "ComfyUI-Advanced-ControlNet"
+ }
+ ],
+ "https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved": [
+ [
+ "ADE_AdjustPEFullStretch",
+ "ADE_AdjustPEManual",
+ "ADE_AdjustPESweetspotStretch",
+ "ADE_AnimateDiffCombine",
+ "ADE_AnimateDiffKeyframe",
+ "ADE_AnimateDiffLoRALoader",
+ "ADE_AnimateDiffLoaderGen1",
+ "ADE_AnimateDiffLoaderV1Advanced",
+ "ADE_AnimateDiffLoaderWithContext",
+ "ADE_AnimateDiffModelSettings",
+ "ADE_AnimateDiffModelSettingsAdvancedAttnStrengths",
+ "ADE_AnimateDiffModelSettingsSimple",
+ "ADE_AnimateDiffModelSettings_Release",
+ "ADE_AnimateDiffSamplingSettings",
+ "ADE_AnimateDiffSettings",
+ "ADE_AnimateDiffUniformContextOptions",
+ "ADE_AnimateDiffUnload",
+ "ADE_ApplyAnimateDiffModel",
+ "ADE_ApplyAnimateDiffModelSimple",
+ "ADE_BatchedContextOptions",
+ "ADE_CustomCFG",
+ "ADE_CustomCFGKeyframe",
+ "ADE_EmptyLatentImageLarge",
+ "ADE_IterationOptsDefault",
+ "ADE_IterationOptsFreeInit",
+ "ADE_LoadAnimateDiffModel",
+ "ADE_LoopedUniformContextOptions",
+ "ADE_LoopedUniformViewOptions",
+ "ADE_MaskedLoadLora",
+ "ADE_MultivalDynamic",
+ "ADE_MultivalScaledMask",
+ "ADE_NoiseLayerAdd",
+ "ADE_NoiseLayerAddWeighted",
+ "ADE_NoiseLayerReplace",
+ "ADE_RawSigmaSchedule",
+ "ADE_SigmaSchedule",
+ "ADE_SigmaScheduleSplitAndCombine",
+ "ADE_SigmaScheduleWeightedAverage",
+ "ADE_SigmaScheduleWeightedAverageInterp",
+ "ADE_StandardStaticContextOptions",
+ "ADE_StandardStaticViewOptions",
+ "ADE_StandardUniformContextOptions",
+ "ADE_StandardUniformViewOptions",
+ "ADE_UseEvolvedSampling",
+ "ADE_ViewsOnlyContextOptions",
+ "AnimateDiffLoaderV1",
+ "CheckpointLoaderSimpleWithNoiseSelect"
+ ],
+ {
+ "title_aux": "AnimateDiff Evolved"
+ }
+ ],
+ "https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite": [
+ [
+ "VHS_BatchManager",
+ "VHS_DuplicateImages",
+ "VHS_DuplicateLatents",
+ "VHS_DuplicateMasks",
+ "VHS_GetImageCount",
+ "VHS_GetLatentCount",
+ "VHS_GetMaskCount",
+ "VHS_LoadAudio",
+ "VHS_LoadImages",
+ "VHS_LoadImagesPath",
+ "VHS_LoadVideo",
+ "VHS_LoadVideoPath",
+ "VHS_MergeImages",
+ "VHS_MergeLatents",
+ "VHS_MergeMasks",
+ "VHS_PruneOutputs",
+ "VHS_SelectEveryNthImage",
+ "VHS_SelectEveryNthLatent",
+ "VHS_SelectEveryNthMask",
+ "VHS_SplitImages",
+ "VHS_SplitLatents",
+ "VHS_SplitMasks",
+ "VHS_VAEDecodeBatched",
+ "VHS_VAEEncodeBatched",
+ "VHS_VideoCombine"
+ ],
+ {
+ "title_aux": "ComfyUI-VideoHelperSuite"
+ }
+ ],
+ "https://github.com/LEv145/images-grid-comfy-plugin": [
+ [
+ "GridAnnotation",
+ "ImageCombine",
+ "ImagesGridByColumns",
+ "ImagesGridByRows",
+ "LatentCombine"
+ ],
+ {
+ "title_aux": "ImagesGrid"
+ }
+ ],
+ "https://github.com/LarryJane491/Image-Captioning-in-ComfyUI": [
+ [
+ "LoRA Caption Load",
+ "LoRA Caption Save"
+ ],
+ {
+ "title_aux": "Image-Captioning-in-ComfyUI"
+ }
+ ],
+ "https://github.com/LarryJane491/Lora-Training-in-Comfy": [
+ [
+ "Lora Training in Comfy (Advanced)",
+ "Lora Training in ComfyUI",
+ "Tensorboard Access"
+ ],
+ {
+ "title_aux": "Lora-Training-in-Comfy"
+ }
+ ],
+ "https://github.com/Layer-norm/comfyui-lama-remover": [
+ [
+ "LamaRemover",
+ "LamaRemoverIMG"
+ ],
+ {
+ "title_aux": "Comfyui lama remover"
+ }
+ ],
+ "https://github.com/Lerc/canvas_tab": [
+ [
+ "Canvas_Tab",
+ "Send_To_Editor"
+ ],
+ {
+ "author": "Lerc",
+ "description": "This extension provides a full page image editor with mask support. There are two nodes, one to receive images from the editor and one to send images to the editor.",
+ "nickname": "Canvas Tab",
+ "title": "Canvas Tab",
+ "title_aux": "Canvas Tab"
+ }
+ ],
+ "https://github.com/Limitex/ComfyUI-Calculation": [
+ [
+ "CenterCalculation",
+ "CreateQRCode"
+ ],
+ {
+ "title_aux": "ComfyUI-Calculation"
+ }
+ ],
+ "https://github.com/Limitex/ComfyUI-Diffusers": [
+ [
+ "CreateIntListNode",
+ "DiffusersClipTextEncode",
+ "DiffusersModelMakeup",
+ "DiffusersPipelineLoader",
+ "DiffusersSampler",
+ "DiffusersSchedulerLoader",
+ "DiffusersVaeLoader",
+ "LcmLoraLoader",
+ "StreamDiffusionCreateStream",
+ "StreamDiffusionFastSampler",
+ "StreamDiffusionSampler",
+ "StreamDiffusionWarmup"
+ ],
+ {
+ "title_aux": "ComfyUI-Diffusers"
+ }
+ ],
+ "https://github.com/Loewen-Hob/rembg-comfyui-node-better": [
+ [
+ "Image Remove Background (rembg)"
+ ],
+ {
+ "title_aux": "Rembg Background Removal Node for ComfyUI"
+ }
+ ],
+ "https://github.com/LonicaMewinsky/ComfyUI-MakeFrame": [
+ [
+ "BreakFrames",
+ "BreakGrid",
+ "GetKeyFrames",
+ "MakeGrid",
+ "RandomImageFromDir"
+ ],
+ {
+ "title_aux": "ComfyBreakAnim"
+ }
+ ],
+ "https://github.com/LonicaMewinsky/ComfyUI-RawSaver": [
+ [
+ "SaveTifImage"
+ ],
+ {
+ "title_aux": "ComfyUI-RawSaver"
+ }
+ ],
+ "https://github.com/LyazS/comfyui-anime-seg": [
+ [
+ "Anime Character Seg"
+ ],
+ {
+ "title_aux": "Anime Character Segmentation node for comfyui"
+ }
+ ],
+ "https://github.com/M1kep/ComfyLiterals": [
+ [
+ "Checkpoint",
+ "Float",
+ "Int",
+ "KepStringLiteral",
+ "Lora",
+ "Operation",
+ "String"
+ ],
+ {
+ "title_aux": "ComfyLiterals"
+ }
+ ],
+ "https://github.com/M1kep/ComfyUI-KepOpenAI": [
+ [
+ "KepOpenAI_ImageWithPrompt"
+ ],
+ {
+ "title_aux": "ComfyUI-KepOpenAI"
+ }
+ ],
+ "https://github.com/M1kep/ComfyUI-OtherVAEs": [
+ [
+ "OtherVAE_Taesd"
+ ],
+ {
+ "title_aux": "ComfyUI-OtherVAEs"
+ }
+ ],
+ "https://github.com/M1kep/Comfy_KepKitchenSink": [
+ [
+ "KepRotateImage"
+ ],
+ {
+ "title_aux": "Comfy_KepKitchenSink"
+ }
+ ],
+ "https://github.com/M1kep/Comfy_KepListStuff": [
+ [
+ "Empty Images",
+ "Image Overlay",
+ "ImageListLoader",
+ "Join Float Lists",
+ "Join Image Lists",
+ "KepStringList",
+ "KepStringListFromNewline",
+ "Kep_JoinListAny",
+ "Kep_RepeatList",
+ "Kep_ReverseList",
+ "Kep_VariableImageBuilder",
+ "List Length",
+ "Range(Num Steps) - Float",
+ "Range(Num Steps) - Int",
+ "Range(Step) - Float",
+ "Range(Step) - Int",
+ "Stack Images",
+ "XYAny",
+ "XYImage"
+ ],
+ {
+ "title_aux": "Comfy_KepListStuff"
+ }
+ ],
+ "https://github.com/M1kep/Comfy_KepMatteAnything": [
+ [
+ "MatteAnything_DinoBoxes",
+ "MatteAnything_GenerateVITMatte",
+ "MatteAnything_InitSamPredictor",
+ "MatteAnything_LoadDINO",
+ "MatteAnything_LoadVITMatteModel",
+ "MatteAnything_SAMLoader",
+ "MatteAnything_SAMMaskFromBoxes",
+ "MatteAnything_ToTrimap"
+ ],
+ {
+ "title_aux": "Comfy_KepMatteAnything"
+ }
+ ],
+ "https://github.com/M1kep/KepPromptLang": [
+ [
+ "Build Gif",
+ "Special CLIP Loader"
+ ],
+ {
+ "title_aux": "KepPromptLang"
+ }
+ ],
+ "https://github.com/MNeMoNiCuZ/ComfyUI-mnemic-nodes": [
+ [
+ "Save Text File_mne"
+ ],
+ {
+ "title_aux": "ComfyUI-mnemic-nodes"
+ }
+ ],
+ "https://github.com/Mamaaaamooooo/batchImg-rembg-ComfyUI-nodes": [
+ [
+ "Image Remove Background (rembg)"
+ ],
+ {
+ "title_aux": "Batch Rembg for ComfyUI"
+ }
+ ],
+ "https://github.com/ManglerFTW/ComfyI2I": [
+ [
+ "Color Transfer",
+ "Combine and Paste",
+ "Inpaint Segments",
+ "Mask Ops"
+ ],
+ {
+ "author": "ManglerFTW",
+ "title": "ComfyI2I",
+ "title_aux": "ComfyI2I"
+ }
+ ],
+ "https://github.com/MarkoCa1/ComfyUI_Segment_Mask": [
+ [
+ "AutomaticMask(segment anything)"
+ ],
+ {
+ "title_aux": "ComfyUI_Segment_Mask"
+ }
+ ],
+ "https://github.com/Miosp/ComfyUI-FBCNN": [
+ [
+ "JPEG artifacts removal FBCNN"
+ ],
+ {
+ "title_aux": "ComfyUI-FBCNN"
+ }
+ ],
+ "https://github.com/MitoshiroPJ/comfyui_slothful_attention": [
+ [
+ "NearSightedAttention",
+ "NearSightedAttentionSimple",
+ "NearSightedTile",
+ "SlothfulAttention"
+ ],
+ {
+ "title_aux": "ComfyUI Slothful Attention"
+ }
+ ],
+ "https://github.com/MrForExample/ComfyUI-3D-Pack": [
+ [],
+ {
+ "nodename_pattern": "^\\[Comfy3D\\]",
+ "title_aux": "ComfyUI-3D-Pack"
+ }
+ ],
+ "https://github.com/MrForExample/ComfyUI-AnimateAnyone-Evolved": [
+ [],
+ {
+ "nodename_pattern": "^\\[AnimateAnyone\\]",
+ "title_aux": "ComfyUI-AnimateAnyone-Evolved"
+ }
+ ],
+ "https://github.com/NicholasMcCarthy/ComfyUI_TravelSuite": [
+ [
+ "LatentTravel"
+ ],
+ {
+ "title_aux": "ComfyUI_TravelSuite"
+ }
+ ],
+ "https://github.com/NimaNzrii/comfyui-photoshop": [
+ [
+ "PhotoshopToComfyUI"
+ ],
+ {
+ "title_aux": "comfyui-photoshop"
+ }
+ ],
+ "https://github.com/NimaNzrii/comfyui-popup_preview": [
+ [
+ "PreviewPopup"
+ ],
+ {
+ "title_aux": "comfyui-popup_preview"
+ }
+ ],
+ "https://github.com/Niutonian/ComfyUi-NoodleWebcam": [
+ [
+ "WebcamNode"
+ ],
+ {
+ "title_aux": "ComfyUi-NoodleWebcam"
+ }
+ ],
+ "https://github.com/Nlar/ComfyUI_CartoonSegmentation": [
+ [
+ "AnimeSegmentation",
+ "KenBurnsConfigLoader",
+ "KenBurns_Processor",
+ "LoadImageFilename"
+ ],
+ {
+ "author": "Nels Larsen",
+ "description": "This extension offers a front end to the Cartoon Segmentation Project (https://github.com/CartoonSegmentation/CartoonSegmentation)",
+ "nickname": "CfyCS",
+ "title": "ComfyUI_CartoonSegmentation",
+ "title_aux": "ComfyUI_CartoonSegmentation"
+ }
+ ],
+ "https://github.com/NotHarroweD/Harronode": [
+ [
+ "Harronode"
+ ],
+ {
+ "author": "HarroweD and quadmoon (https://github.com/traugdor)",
+ "description": "This extension to ComfyUI will build a prompt for the Harrlogos LoRA for SDXL.",
+ "nickname": "Harronode",
+ "nodename_pattern": "Harronode",
+ "title": "Harrlogos Prompt Builder Node",
+ "title_aux": "Harronode"
+ }
+ ],
+ "https://github.com/Nourepide/ComfyUI-Allor": [
+ [
+ "AlphaChanelAdd",
+ "AlphaChanelAddByMask",
+ "AlphaChanelAsMask",
+ "AlphaChanelRemove",
+ "AlphaChanelRestore",
+ "ClipClamp",
+ "ClipVisionClamp",
+ "ClipVisionOutputClamp",
+ "ConditioningClamp",
+ "ControlNetClamp",
+ "GligenClamp",
+ "ImageBatchCopy",
+ "ImageBatchFork",
+ "ImageBatchGet",
+ "ImageBatchJoin",
+ "ImageBatchPermute",
+ "ImageBatchRemove",
+ "ImageClamp",
+ "ImageCompositeAbsolute",
+ "ImageCompositeAbsoluteByContainer",
+ "ImageCompositeRelative",
+ "ImageCompositeRelativeByContainer",
+ "ImageContainer",
+ "ImageContainerInheritanceAdd",
+ "ImageContainerInheritanceMax",
+ "ImageContainerInheritanceScale",
+ "ImageContainerInheritanceSum",
+ "ImageDrawArc",
+ "ImageDrawArcByContainer",
+ "ImageDrawChord",
+ "ImageDrawChordByContainer",
+ "ImageDrawEllipse",
+ "ImageDrawEllipseByContainer",
+ "ImageDrawLine",
+ "ImageDrawLineByContainer",
+ "ImageDrawPieslice",
+ "ImageDrawPiesliceByContainer",
+ "ImageDrawPolygon",
+ "ImageDrawRectangle",
+ "ImageDrawRectangleByContainer",
+ "ImageDrawRectangleRounded",
+ "ImageDrawRectangleRoundedByContainer",
+ "ImageEffectsAdjustment",
+ "ImageEffectsGrayscale",
+ "ImageEffectsLensBokeh",
+ "ImageEffectsLensChromaticAberration",
+ "ImageEffectsLensOpticAxis",
+ "ImageEffectsLensVignette",
+ "ImageEffectsLensZoomBurst",
+ "ImageEffectsNegative",
+ "ImageEffectsSepia",
+ "ImageFilterBilateralBlur",
+ "ImageFilterBlur",
+ "ImageFilterBoxBlur",
+ "ImageFilterContour",
+ "ImageFilterDetail",
+ "ImageFilterEdgeEnhance",
+ "ImageFilterEdgeEnhanceMore",
+ "ImageFilterEmboss",
+ "ImageFilterFindEdges",
+ "ImageFilterGaussianBlur",
+ "ImageFilterGaussianBlurAdvanced",
+ "ImageFilterMax",
+ "ImageFilterMedianBlur",
+ "ImageFilterMin",
+ "ImageFilterMode",
+ "ImageFilterRank",
+ "ImageFilterSharpen",
+ "ImageFilterSmooth",
+ "ImageFilterSmoothMore",
+ "ImageFilterStackBlur",
+ "ImageNoiseBeta",
+ "ImageNoiseBinomial",
+ "ImageNoiseBytes",
+ "ImageNoiseGaussian",
+ "ImageSegmentation",
+ "ImageSegmentationCustom",
+ "ImageSegmentationCustomAdvanced",
+ "ImageText",
+ "ImageTextMultiline",
+ "ImageTextMultilineOutlined",
+ "ImageTextOutlined",
+ "ImageTransformCropAbsolute",
+ "ImageTransformCropCorners",
+ "ImageTransformCropRelative",
+ "ImageTransformPaddingAbsolute",
+ "ImageTransformPaddingRelative",
+ "ImageTransformResizeAbsolute",
+ "ImageTransformResizeClip",
+ "ImageTransformResizeRelative",
+ "ImageTransformRotate",
+ "ImageTransformTranspose",
+ "LatentClamp",
+ "MaskClamp",
+ "ModelClamp",
+ "StyleModelClamp",
+ "UpscaleModelClamp",
+ "VaeClamp"
+ ],
+ {
+ "title_aux": "Allor Plugin"
+ }
+ ],
+ "https://github.com/Nuked88/ComfyUI-N-Nodes": [
+ [
+ "CLIPTextEncodeAdvancedNSuite [n-suite]",
+ "DynamicPrompt [n-suite]",
+ "Float Variable [n-suite]",
+ "FrameInterpolator [n-suite]",
+ "GPT Loader Simple [n-suite]",
+ "GPT Sampler [n-suite]",
+ "ImagePadForOutpaintAdvanced [n-suite]",
+ "Integer Variable [n-suite]",
+ "Llava Clip Loader [n-suite]",
+ "LoadFramesFromFolder [n-suite]",
+ "LoadVideo [n-suite]",
+ "SaveVideo [n-suite]",
+ "SetMetadataForSaveVideo [n-suite]",
+ "String Variable [n-suite]"
+ ],
+ {
+ "title_aux": "ComfyUI-N-Nodes"
+ }
+ ],
+ "https://github.com/Off-Live/ComfyUI-off-suite": [
+ [
+ "Apply CLAHE",
+ "Cached Image Load From URL",
+ "Crop Center wigh SEGS",
+ "Crop Center with SEGS",
+ "Dilate Mask for Each Face",
+ "GW Number Formatting",
+ "Image Crop Fit",
+ "Image Resize Fit",
+ "OFF SEGS to Image",
+ "Paste Face Segment to Image",
+ "Query Gender and Age",
+ "SEGS to Face Crop Data",
+ "Safe Mask to Image",
+ "VAE Encode For Inpaint V2",
+ "Watermarking"
+ ],
+ {
+ "title_aux": "ComfyUI-off-suite"
+ }
+ ],
+ "https://github.com/Onierous/QRNG_Node_ComfyUI/raw/main/qrng_node.py": [
+ [
+ "QRNG_Node_CSV"
+ ],
+ {
+ "title_aux": "QRNG_Node_ComfyUI"
+ }
+ ],
+ "https://github.com/PCMonsterx/ComfyUI-CSV-Loader": [
+ [
+ "Load Artists CSV",
+ "Load Artmovements CSV",
+ "Load Characters CSV",
+ "Load Colors CSV",
+ "Load Composition CSV",
+ "Load Lighting CSV",
+ "Load Negative CSV",
+ "Load Positive CSV",
+ "Load Settings CSV",
+ "Load Styles CSV"
+ ],
+ {
+ "title_aux": "ComfyUI-CSV-Loader"
+ }
+ ],
+ "https://github.com/ParmanBabra/ComfyUI-Malefish-Custom-Scripts": [
+ [
+ "CSVPromptsLoader",
+ "CombinePrompt",
+ "MultiLoraLoader",
+ "RandomPrompt"
+ ],
+ {
+ "title_aux": "ComfyUI-Malefish-Custom-Scripts"
+ }
+ ],
+ "https://github.com/Pfaeff/pfaeff-comfyui": [
+ [
+ "AstropulsePixelDetector",
+ "BackgroundRemover",
+ "ImagePadForBetterOutpaint",
+ "Inpainting",
+ "InpaintingPipelineLoader"
+ ],
+ {
+ "title_aux": "pfaeff-comfyui"
+ }
+ ],
+ "https://github.com/QaisMalkawi/ComfyUI-QaisHelper": [
+ [
+ "Bool Binary Operation",
+ "Bool Unary Operation",
+ "Item Debugger",
+ "Item Switch",
+ "Nearest SDXL Resolution",
+ "SDXL Resolution",
+ "Size Swapper"
+ ],
+ {
+ "title_aux": "ComfyUI-Qais-Helper"
+ }
+ ],
+ "https://github.com/RenderRift/ComfyUI-RenderRiftNodes": [
+ [
+ "AnalyseMetadata",
+ "DateIntegerNode",
+ "DisplayMetaOptions",
+ "LoadImageWithMeta",
+ "MetadataOverlayNode",
+ "VideoPathMetaExtraction"
+ ],
+ {
+ "title_aux": "ComfyUI-RenderRiftNodes"
+ }
+ ],
+ "https://github.com/Ryuukeisyou/comfyui_face_parsing": [
+ [
+ "BBoxListItemSelect(FaceParsing)",
+ "BBoxResize(FaceParsing)",
+ "ColorAdjust(FaceParsing)",
+ "FaceBBoxDetect(FaceParsing)",
+ "FaceBBoxDetectorLoader(FaceParsing)",
+ "FaceParse(FaceParsing)",
+ "FaceParsingModelLoader(FaceParsing)",
+ "FaceParsingProcessorLoader(FaceParsing)",
+ "FaceParsingResultsParser(FaceParsing)",
+ "GuidedFilter(FaceParsing)",
+ "ImageCropWithBBox(FaceParsing)",
+ "ImageInsertWithBBox(FaceParsing)",
+ "ImageListSelect(FaceParsing)",
+ "ImagePadWithBBox(FaceParsing)",
+ "ImageResizeCalculator(FaceParsing)",
+ "ImageResizeWithBBox(FaceParsing)",
+ "ImageSize(FaceParsing)",
+ "LatentCropWithBBox(FaceParsing)",
+ "LatentInsertWithBBox(FaceParsing)",
+ "LatentSize(FaceParsing)",
+ "MaskComposite(FaceParsing)",
+ "MaskListComposite(FaceParsing)",
+ "MaskListSelect(FaceParsing)",
+ "MaskToBBox(FaceParsing)",
+ "SkinDetectTraditional(FaceParsing)"
+ ],
+ {
+ "title_aux": "comfyui_face_parsing"
+ }
+ ],
+ "https://github.com/Ryuukeisyou/comfyui_image_io_helpers": [
+ [
+ "ImageLoadAsMaskByPath(ImageIOHelpers)",
+ "ImageLoadByPath(ImageIOHelpers)",
+ "ImageLoadFromBase64(ImageIOHelpers)",
+ "ImageSaveAsBase64(ImageIOHelpers)",
+ "ImageSaveToPath(ImageIOHelpers)"
+ ],
+ {
+ "title_aux": "comfyui_image_io_helpers"
+ }
+ ],
+ "https://github.com/SLAPaper/ComfyUI-Image-Selector": [
+ [
+ "ImageDuplicator",
+ "ImageSelector",
+ "LatentDuplicator",
+ "LatentSelector"
+ ],
+ {
+ "title_aux": "ComfyUI-Image-Selector"
+ }
+ ],
+ "https://github.com/SOELexicon/ComfyUI-LexMSDBNodes": [
+ [
+ "MSSqlSelectNode",
+ "MSSqlTableNode"
+ ],
+ {
+ "title_aux": "LexMSDBNodes"
+ }
+ ],
+ "https://github.com/SOELexicon/ComfyUI-LexTools": [
+ [
+ "AgeClassifierNode",
+ "ArtOrHumanClassifierNode",
+ "DocumentClassificationNode",
+ "FoodCategoryClassifierNode",
+ "ImageAspectPadNode",
+ "ImageCaptioning",
+ "ImageFilterByFloatScoreNode",
+ "ImageFilterByIntScoreNode",
+ "ImageQualityScoreNode",
+ "ImageRankingNode",
+ "ImageScaleToMin",
+ "MD5ImageHashNode",
+ "SamplerPropertiesNode",
+ "ScoreConverterNode",
+ "SeedIncrementerNode",
+ "SegformerNode",
+ "SegformerNodeMasks",
+ "SegformerNodeMergeSegments",
+ "StepCfgIncrementNode"
+ ],
+ {
+ "title_aux": "ComfyUI-LexTools"
+ }
+ ],
+ "https://github.com/SadaleNet/CLIPTextEncodeA1111-ComfyUI/raw/master/custom_nodes/clip_text_encoder_a1111.py": [
+ [
+ "CLIPTextEncodeA1111",
+ "RerouteTextForCLIPTextEncodeA1111"
+ ],
+ {
+ "title_aux": "ComfyUI A1111-like Prompt Custom Node Solution"
+ }
+ ],
+ "https://github.com/Scholar01/ComfyUI-Keyframe": [
+ [
+ "KeyframeApply",
+ "KeyframeInterpolationPart",
+ "KeyframePart"
+ ],
+ {
+ "title_aux": "SComfyUI-Keyframe"
+ }
+ ],
+ "https://github.com/SeargeDP/SeargeSDXL": [
+ [
+ "SeargeAdvancedParameters",
+ "SeargeCheckpointLoader",
+ "SeargeConditionMixing",
+ "SeargeConditioningMuxer2",
+ "SeargeConditioningMuxer5",
+ "SeargeConditioningParameters",
+ "SeargeControlnetAdapterV2",
+ "SeargeControlnetModels",
+ "SeargeCustomAfterUpscaling",
+ "SeargeCustomAfterVaeDecode",
+ "SeargeCustomPromptMode",
+ "SeargeDebugPrinter",
+ "SeargeEnablerInputs",
+ "SeargeFloatConstant",
+ "SeargeFloatMath",
+ "SeargeFloatPair",
+ "SeargeFreeU",
+ "SeargeGenerated1",
+ "SeargeGenerationParameters",
+ "SeargeHighResolution",
+ "SeargeImage2ImageAndInpainting",
+ "SeargeImageAdapterV2",
+ "SeargeImageSave",
+ "SeargeImageSaving",
+ "SeargeInput1",
+ "SeargeInput2",
+ "SeargeInput3",
+ "SeargeInput4",
+ "SeargeInput5",
+ "SeargeInput6",
+ "SeargeInput7",
+ "SeargeIntegerConstant",
+ "SeargeIntegerMath",
+ "SeargeIntegerPair",
+ "SeargeIntegerScaler",
+ "SeargeLatentMuxer3",
+ "SeargeLoraLoader",
+ "SeargeLoras",
+ "SeargeMagicBox",
+ "SeargeModelSelector",
+ "SeargeOperatingMode",
+ "SeargeOutput1",
+ "SeargeOutput2",
+ "SeargeOutput3",
+ "SeargeOutput4",
+ "SeargeOutput5",
+ "SeargeOutput6",
+ "SeargeOutput7",
+ "SeargeParameterProcessor",
+ "SeargePipelineStart",
+ "SeargePipelineTerminator",
+ "SeargePreviewImage",
+ "SeargePromptAdapterV2",
+ "SeargePromptCombiner",
+ "SeargePromptStyles",
+ "SeargePromptText",
+ "SeargeSDXLBasePromptEncoder",
+ "SeargeSDXLImage2ImageSampler",
+ "SeargeSDXLImage2ImageSampler2",
+ "SeargeSDXLPromptEncoder",
+ "SeargeSDXLRefinerPromptEncoder",
+ "SeargeSDXLSampler",
+ "SeargeSDXLSampler2",
+ "SeargeSDXLSamplerV3",
+ "SeargeSamplerAdvanced",
+ "SeargeSamplerInputs",
+ "SeargeSaveFolderInputs",
+ "SeargeSeparator",
+ "SeargeStylePreprocessor",
+ "SeargeTextInputV2",
+ "SeargeUpscaleModelLoader",
+ "SeargeUpscaleModels",
+ "SeargeVAELoader"
+ ],
+ {
+ "title_aux": "SeargeSDXL"
+ }
+ ],
+ "https://github.com/Ser-Hilary/SDXL_sizing/raw/main/conditioning_sizing_for_SDXL.py": [
+ [
+ "get_aspect_from_image",
+ "get_aspect_from_ints",
+ "sizing_node",
+ "sizing_node_basic",
+ "sizing_node_unparsed"
+ ],
+ {
+ "title_aux": "SDXL_sizing"
+ }
+ ],
+ "https://github.com/ShmuelRonen/ComfyUI-SVDResizer": [
+ [
+ "SVDRsizer"
+ ],
+ {
+ "title_aux": "ComfyUI-SVDResizer"
+ }
+ ],
+ "https://github.com/Shraknard/ComfyUI-Remover": [
+ [
+ "Remover"
+ ],
+ {
+ "title_aux": "ComfyUI-Remover"
+ }
+ ],
+ "https://github.com/Siberpone/lazy-pony-prompter": [
+ [
+ "LPP_Deleter",
+ "LPP_Derpibooru",
+ "LPP_E621",
+ "LPP_Loader_Derpibooru",
+ "LPP_Loader_E621",
+ "LPP_Saver"
+ ],
+ {
+ "title_aux": "Lazy Pony Prompter"
+ }
+ ],
+ "https://github.com/Smuzzies/comfyui_chatbox_overlay/raw/main/chatbox_overlay.py": [
+ [
+ "Chatbox Overlay"
+ ],
+ {
+ "title_aux": "Chatbox Overlay node for ComfyUI"
+ }
+ ],
+ "https://github.com/SoftMeng/ComfyUI_Mexx_Poster": [
+ [
+ "ComfyUI_Mexx_Poster"
+ ],
+ {
+ "title_aux": "ComfyUI_Mexx_Poster"
+ }
+ ],
+ "https://github.com/SoftMeng/ComfyUI_Mexx_Styler": [
+ [
+ "MexxSDXLPromptStyler",
+ "MexxSDXLPromptStylerAdvanced"
+ ],
+ {
+ "title_aux": "ComfyUI_Mexx_Styler"
+ }
+ ],
+ "https://github.com/SpaceKendo/ComfyUI-svd_txt2vid": [
+ [
+ "SVD_txt2vid_ConditioningwithLatent"
+ ],
+ {
+ "title_aux": "Text to video for Stable Video Diffusion in ComfyUI"
+ }
+ ],
+ "https://github.com/Stability-AI/stability-ComfyUI-nodes": [
+ [
+ "ColorBlend",
+ "ControlLoraSave",
+ "GetImageSize"
+ ],
+ {
+ "title_aux": "stability-ComfyUI-nodes"
+ }
+ ],
+ "https://github.com/StartHua/ComfyUI_Seg_VITON": [
+ [
+ "segformer_agnostic",
+ "segformer_clothes",
+ "segformer_remove_bg",
+ "stabel_vition"
+ ],
+ {
+ "title_aux": "ComfyUI_Seg_VITON"
+ }
+ ],
+ "https://github.com/StartHua/Comfyui_joytag": [
+ [
+ "CXH_JoyTag"
+ ],
+ {
+ "title_aux": "Comfyui_joytag"
+ }
+ ],
+ "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes": [
+ [
+ "CR 8 Channel In",
+ "CR 8 Channel Out",
+ "CR Apply ControlNet",
+ "CR Apply LoRA Stack",
+ "CR Apply Model Merge",
+ "CR Apply Multi Upscale",
+ "CR Apply Multi-ControlNet",
+ "CR Arabic Text RTL",
+ "CR Aspect Ratio",
+ "CR Aspect Ratio Banners",
+ "CR Aspect Ratio SDXL",
+ "CR Aspect Ratio Social Media",
+ "CR Batch Images From List",
+ "CR Batch Process Switch",
+ "CR Binary Pattern",
+ "CR Binary To Bit List",
+ "CR Bit Schedule",
+ "CR Central Schedule",
+ "CR Checker Pattern",
+ "CR Clamp Value",
+ "CR Clip Input Switch",
+ "CR Color Bars",
+ "CR Color Gradient",
+ "CR Color Panel",
+ "CR Color Tint",
+ "CR Combine Prompt",
+ "CR Combine Schedules",
+ "CR Comic Panel Templates",
+ "CR Composite Text",
+ "CR Conditioning Input Switch",
+ "CR Conditioning Mixer",
+ "CR ControlNet Input Switch",
+ "CR Current Frame",
+ "CR Cycle Images",
+ "CR Cycle Images Simple",
+ "CR Cycle LoRAs",
+ "CR Cycle Models",
+ "CR Cycle Text",
+ "CR Cycle Text Simple",
+ "CR Data Bus In",
+ "CR Data Bus Out",
+ "CR Debatch Frames",
+ "CR Diamond Panel",
+ "CR Draw Perspective Text",
+ "CR Draw Pie",
+ "CR Draw Shape",
+ "CR Draw Text",
+ "CR Encode Scheduled Prompts",
+ "CR Feathered Border",
+ "CR Float Range List",
+ "CR Float To Integer",
+ "CR Float To String",
+ "CR Font File List",
+ "CR Get Parameter From Prompt",
+ "CR Gradient Float",
+ "CR Gradient Integer",
+ "CR Half Drop Panel",
+ "CR Halftone Filter",
+ "CR Halftone Grid",
+ "CR Hires Fix Process Switch",
+ "CR Image Border",
+ "CR Image Grid Panel",
+ "CR Image Input Switch",
+ "CR Image Input Switch (4 way)",
+ "CR Image List",
+ "CR Image List Simple",
+ "CR Image Output",
+ "CR Image Panel",
+ "CR Image Pipe Edit",
+ "CR Image Pipe In",
+ "CR Image Pipe Out",
+ "CR Image Size",
+ "CR Img2Img Process Switch",
+ "CR Increment Float",
+ "CR Increment Integer",
+ "CR Index",
+ "CR Index Increment",
+ "CR Index Multiply",
+ "CR Index Reset",
+ "CR Input Text List",
+ "CR Integer Multiple",
+ "CR Integer Range List",
+ "CR Integer To String",
+ "CR Interpolate Latents",
+ "CR Intertwine Lists",
+ "CR Keyframe List",
+ "CR Latent Batch Size",
+ "CR Latent Input Switch",
+ "CR LoRA List",
+ "CR LoRA Stack",
+ "CR Load Animation Frames",
+ "CR Load Flow Frames",
+ "CR Load GIF As List",
+ "CR Load Image List",
+ "CR Load Image List Plus",
+ "CR Load LoRA",
+ "CR Load Prompt Style",
+ "CR Load Schedule From File",
+ "CR Load Scheduled ControlNets",
+ "CR Load Scheduled LoRAs",
+ "CR Load Scheduled Models",
+ "CR Load Text List",
+ "CR Mask Text",
+ "CR Math Operation",
+ "CR Model Input Switch",
+ "CR Model List",
+ "CR Model Merge Stack",
+ "CR Module Input",
+ "CR Module Output",
+ "CR Module Pipe Loader",
+ "CR Multi Upscale Stack",
+ "CR Multi-ControlNet Stack",
+ "CR Multiline Text",
+ "CR Output Flow Frames",
+ "CR Output Schedule To File",
+ "CR Overlay Text",
+ "CR Overlay Transparent Image",
+ "CR Page Layout",
+ "CR Pipe Switch",
+ "CR Polygons",
+ "CR Prompt List",
+ "CR Prompt List Keyframes",
+ "CR Prompt Scheduler",
+ "CR Prompt Text",
+ "CR Radial Gradient",
+ "CR Random Hex Color",
+ "CR Random LoRA Stack",
+ "CR Random Multiline Colors",
+ "CR Random Multiline Values",
+ "CR Random Panel Codes",
+ "CR Random RGB",
+ "CR Random RGB Gradient",
+ "CR Random Shape Pattern",
+ "CR Random Weight LoRA",
+ "CR Repeater",
+ "CR SD1.5 Aspect Ratio",
+ "CR SDXL Aspect Ratio",
+ "CR SDXL Base Prompt Encoder",
+ "CR SDXL Prompt Mix Presets",
+ "CR SDXL Prompt Mixer",
+ "CR SDXL Style Text",
+ "CR Save Text To File",
+ "CR Schedule Input Switch",
+ "CR Schedule To ScheduleList",
+ "CR Seamless Checker",
+ "CR Seed",
+ "CR Seed to Int",
+ "CR Select Font",
+ "CR Select ISO Size",
+ "CR Select Model",
+ "CR Select Resize Method",
+ "CR Set Switch From String",
+ "CR Set Value On Binary",
+ "CR Set Value On Boolean",
+ "CR Set Value on String",
+ "CR Simple Banner",
+ "CR Simple Binary Pattern",
+ "CR Simple Binary Pattern Simple",
+ "CR Simple Image Compare",
+ "CR Simple List",
+ "CR Simple Meme Template",
+ "CR Simple Prompt List",
+ "CR Simple Prompt List Keyframes",
+ "CR Simple Prompt Scheduler",
+ "CR Simple Schedule",
+ "CR Simple Text Panel",
+ "CR Simple Text Scheduler",
+ "CR Simple Text Watermark",
+ "CR Simple Titles",
+ "CR Simple Value Scheduler",
+ "CR Split String",
+ "CR Starburst Colors",
+ "CR Starburst Lines",
+ "CR String To Boolean",
+ "CR String To Combo",
+ "CR String To Number",
+ "CR Style Bars",
+ "CR Switch Model and CLIP",
+ "CR Text",
+ "CR Text Blacklist",
+ "CR Text Concatenate",
+ "CR Text Cycler",
+ "CR Text Input Switch",
+ "CR Text Input Switch (4 way)",
+ "CR Text Length",
+ "CR Text List",
+ "CR Text List Simple",
+ "CR Text List To String",
+ "CR Text Operation",
+ "CR Text Replace",
+ "CR Text Scheduler",
+ "CR Thumbnail Preview",
+ "CR Trigger",
+ "CR Upscale Image",
+ "CR VAE Decode",
+ "CR VAE Input Switch",
+ "CR Value",
+ "CR Value Cycler",
+ "CR Value Scheduler",
+ "CR Vignette Filter",
+ "CR XY From Folder",
+ "CR XY Index",
+ "CR XY Interpolate",
+ "CR XY List",
+ "CR XY Product",
+ "CR XY Save Grid Image",
+ "CR XYZ Index",
+ "CR_Aspect Ratio For Print"
+ ],
+ {
+ "author": "Suzie1",
+ "description": "175 custom nodes for artists, designers and animators.",
+ "nickname": "Comfyroll Studio",
+ "title": "Comfyroll Studio",
+ "title_aux": "ComfyUI_Comfyroll_CustomNodes"
+ }
+ ],
+ "https://github.com/Sxela/ComfyWarp": [
+ [
+ "ExtractOpticalFlow",
+ "LoadFrame",
+ "LoadFrameFromDataset",
+ "LoadFrameFromFolder",
+ "LoadFramePairFromDataset",
+ "LoadFrameSequence",
+ "MakeFrameDataset",
+ "MixConsistencyMaps",
+ "OffsetNumber",
+ "ResizeToFit",
+ "SaveFrame",
+ "WarpFrame"
+ ],
+ {
+ "title_aux": "ComfyWarp"
+ }
+ ],
+ "https://github.com/TGu-97/ComfyUI-TGu-utils": [
+ [
+ "MPNReroute",
+ "MPNSwitch",
+ "PNSwitch"
+ ],
+ {
+ "title_aux": "TGu Utilities"
+ }
+ ],
+ "https://github.com/THtianhao/ComfyUI-FaceChain": [
+ [
+ "FC CropAndPaste",
+ "FC CropBottom",
+ "FC CropToOrigin",
+ "FC FaceDetectCrop",
+ "FC FaceFusion",
+ "FC FaceSegAndReplace",
+ "FC FaceSegment",
+ "FC MaskOP",
+ "FC RemoveCannyFace",
+ "FC ReplaceByMask",
+ "FC StyleLoraLoad"
+ ],
+ {
+ "title_aux": "ComfyUI-FaceChain"
+ }
+ ],
+ "https://github.com/THtianhao/ComfyUI-Portrait-Maker": [
+ [
+ "PM_BoxCropImage",
+ "PM_ColorTransfer",
+ "PM_ExpandMaskBox",
+ "PM_FaceFusion",
+ "PM_FaceShapMatch",
+ "PM_FaceSkin",
+ "PM_GetImageInfo",
+ "PM_ImageResizeTarget",
+ "PM_ImageScaleShort",
+ "PM_MakeUpTransfer",
+ "PM_MaskDilateErode",
+ "PM_MaskMerge2Image",
+ "PM_PortraitEnhancement",
+ "PM_RatioMerge2Image",
+ "PM_ReplaceBoxImg",
+ "PM_RetinaFace",
+ "PM_Similarity",
+ "PM_SkinRetouching",
+ "PM_SuperColorTransfer",
+ "PM_SuperMakeUpTransfer"
+ ],
+ {
+ "title_aux": "ComfyUI-Portrait-Maker"
+ }
+ ],
+ "https://github.com/TRI3D-LC/tri3d-comfyui-nodes": [
+ [
+ "tri3d-adjust-neck",
+ "tri3d-atr-parse",
+ "tri3d-atr-parse-batch",
+ "tri3d-clipdrop-bgremove-api",
+ "tri3d-dwpose",
+ "tri3d-extract-hand",
+ "tri3d-extract-parts-batch",
+ "tri3d-extract-parts-batch2",
+ "tri3d-extract-parts-mask-batch",
+ "tri3d-face-recognise",
+ "tri3d-float-to-image",
+ "tri3d-fuzzification",
+ "tri3d-image-mask-2-box",
+ "tri3d-image-mask-box-2-image",
+ "tri3d-interaction-canny",
+ "tri3d-load-pose-json",
+ "tri3d-pose-adaption",
+ "tri3d-pose-to-image",
+ "tri3d-position-hands",
+ "tri3d-position-parts-batch",
+ "tri3d-recolor-mask",
+ "tri3d-recolor-mask-LAB_space",
+ "tri3d-recolor-mask-LAB_space_manual",
+ "tri3d-recolor-mask-RGB_space",
+ "tri3d-skin-feathered-padded-mask",
+ "tri3d-swap-pixels"
+ ],
+ {
+ "title_aux": "tri3d-comfyui-nodes"
+ }
+ ],
+ "https://github.com/Taremin/comfyui-prompt-extranetworks": [
+ [
+ "PromptExtraNetworks"
+ ],
+ {
+ "title_aux": "ComfyUI Prompt ExtraNetworks"
+ }
+ ],
+ "https://github.com/Taremin/comfyui-string-tools": [
+ [
+ "StringToolsBalancedChoice",
+ "StringToolsConcat",
+ "StringToolsRandomChoice",
+ "StringToolsString",
+ "StringToolsText"
+ ],
+ {
+ "title_aux": "ComfyUI String Tools"
+ }
+ ],
+ "https://github.com/TeaCrab/ComfyUI-TeaNodes": [
+ [
+ "TC_ColorFill",
+ "TC_EqualizeCLAHE",
+ "TC_ImageResize",
+ "TC_ImageScale",
+ "TC_RandomColorFill",
+ "TC_SizeApproximation"
+ ],
+ {
+ "title_aux": "ComfyUI-TeaNodes"
+ }
+ ],
+ "https://github.com/TemryL/ComfyS3": [
+ [
+ "DownloadFileS3",
+ "LoadImageS3",
+ "SaveImageS3",
+ "SaveVideoFilesS3",
+ "UploadFileS3"
+ ],
+ {
+ "title_aux": "ComfyS3"
+ }
+ ],
+ "https://github.com/TheBarret/ZSuite": [
+ [
+ "ZSuite: Prompter",
+ "ZSuite: RF Noise",
+ "ZSuite: SeedMod"
+ ],
+ {
+ "title_aux": "ZSuite"
+ }
+ ],
+ "https://github.com/TinyTerra/ComfyUI_tinyterraNodes": [
+ [
+ "ttN busIN",
+ "ttN busOUT",
+ "ttN compareInput",
+ "ttN concat",
+ "ttN debugInput",
+ "ttN float",
+ "ttN hiresfixScale",
+ "ttN imageOutput",
+ "ttN imageREMBG",
+ "ttN int",
+ "ttN multiModelMerge",
+ "ttN pipe2BASIC",
+ "ttN pipe2DETAILER",
+ "ttN pipeEDIT",
+ "ttN pipeEncodeConcat",
+ "ttN pipeIN",
+ "ttN pipeKSampler",
+ "ttN pipeKSamplerAdvanced",
+ "ttN pipeKSamplerSDXL",
+ "ttN pipeLoader",
+ "ttN pipeLoaderSDXL",
+ "ttN pipeLoraStack",
+ "ttN pipeOUT",
+ "ttN seed",
+ "ttN seedDebug",
+ "ttN text",
+ "ttN text3BOX_3WAYconcat",
+ "ttN text7BOX_concat",
+ "ttN textDebug",
+ "ttN xyPlot"
+ ],
+ {
+ "author": "tinyterra",
+ "description": "This extension offers various pipe nodes, fullscreen image viewer based on node history, dynamic widgets, interface customization, and more.",
+ "nickname": "ttNodes",
+ "nodename_pattern": "^ttN ",
+ "title": "tinyterraNodes",
+ "title_aux": "tinyterraNodes"
+ }
+ ],
+ "https://github.com/TripleHeadedMonkey/ComfyUI_MileHighStyler": [
+ [
+ "menus"
+ ],
+ {
+ "title_aux": "ComfyUI_MileHighStyler"
+ }
+ ],
+ "https://github.com/Tropfchen/ComfyUI-Embedding_Picker": [
+ [
+ "EmbeddingPicker"
+ ],
+ {
+ "title_aux": "Embedding Picker"
+ }
+ ],
+ "https://github.com/Tropfchen/ComfyUI-yaResolutionSelector": [
+ [
+ "YARS",
+ "YARSAdv"
+ ],
+ {
+ "title_aux": "YARS: Yet Another Resolution Selector"
+ }
+ ],
+ "https://github.com/Trung0246/ComfyUI-0246": [
+ [
+ "0246.Beautify",
+ "0246.BoxRange",
+ "0246.CastReroute",
+ "0246.Cloud",
+ "0246.Convert",
+ "0246.Count",
+ "0246.Highway",
+ "0246.HighwayBatch",
+ "0246.Hold",
+ "0246.Hub",
+ "0246.Junction",
+ "0246.JunctionBatch",
+ "0246.Loop",
+ "0246.Merge",
+ "0246.Meta",
+ "0246.Pick",
+ "0246.RandomInt",
+ "0246.Script",
+ "0246.ScriptNode",
+ "0246.ScriptPile",
+ "0246.ScriptRule",
+ "0246.Stringify",
+ "0246.Switch"
+ ],
+ {
+ "author": "Trung0246",
+ "description": "Random nodes for ComfyUI I made to solve my struggle with ComfyUI (ex: pipe, process). Have varying quality.",
+ "nickname": "ComfyUI-0246",
+ "title": "ComfyUI-0246",
+ "title_aux": "ComfyUI-0246"
+ }
+ ],
+ "https://github.com/Ttl/ComfyUi_NNLatentUpscale": [
+ [
+ "NNLatentUpscale"
+ ],
+ {
+ "title_aux": "ComfyUI Neural network latent upscale custom node"
+ }
+ ],
+ "https://github.com/Umikaze-job/select_folder_path_easy": [
+ [
+ "SelectFolderPathEasy"
+ ],
+ {
+ "title_aux": "select_folder_path_easy"
+ }
+ ],
+ "https://github.com/WASasquatch/ASTERR": [
+ [
+ "ASTERR",
+ "SaveASTERR"
+ ],
+ {
+ "title_aux": "ASTERR"
+ }
+ ],
+ "https://github.com/WASasquatch/ComfyUI_Preset_Merger": [
+ [
+ "Preset_Model_Merge"
+ ],
+ {
+ "title_aux": "ComfyUI Preset Merger"
+ }
+ ],
+ "https://github.com/WASasquatch/FreeU_Advanced": [
+ [
+ "FreeU (Advanced)",
+ "FreeU_V2 (Advanced)"
+ ],
+ {
+ "title_aux": "FreeU_Advanced"
+ }
+ ],
+ "https://github.com/WASasquatch/PPF_Noise_ComfyUI": [
+ [
+ "Blend Latents (PPF Noise)",
+ "Cross-Hatch Power Fractal (PPF Noise)",
+ "Images as Latents (PPF Noise)",
+ "Perlin Power Fractal Latent (PPF Noise)"
+ ],
+ {
+ "title_aux": "PPF_Noise_ComfyUI"
+ }
+ ],
+ "https://github.com/WASasquatch/PowerNoiseSuite": [
+ [
+ "Blend Latents (PPF Noise)",
+ "Cross-Hatch Power Fractal (PPF Noise)",
+ "Cross-Hatch Power Fractal Settings (PPF Noise)",
+ "Images as Latents (PPF Noise)",
+ "Latent Adjustment (PPF Noise)",
+ "Latents to CPU (PPF Noise)",
+ "Linear Cross-Hatch Power Fractal (PPF Noise)",
+ "Perlin Power Fractal Latent (PPF Noise)",
+ "Perlin Power Fractal Settings (PPF Noise)",
+ "Power KSampler Advanced (PPF Noise)",
+ "Power-Law Noise (PPF Noise)"
+ ],
+ {
+ "title_aux": "Power Noise Suite for ComfyUI"
+ }
+ ],
+ "https://github.com/WASasquatch/WAS_Extras": [
+ [
+ "BLVAEEncode",
+ "CLIPTextEncodeList",
+ "CLIPTextEncodeSequence2",
+ "ConditioningBlend",
+ "DebugInput",
+ "KSamplerSeq",
+ "KSamplerSeq2",
+ "VAEEncodeForInpaint (WAS)",
+ "VividSharpen"
+ ],
+ {
+ "title_aux": "WAS_Extras"
+ }
+ ],
+ "https://github.com/WASasquatch/was-node-suite-comfyui": [
+ [
+ "BLIP Analyze Image",
+ "BLIP Model Loader",
+ "Blend Latents",
+ "Boolean To Text",
+ "Bounded Image Blend",
+ "Bounded Image Blend with Mask",
+ "Bounded Image Crop",
+ "Bounded Image Crop with Mask",
+ "Bus Node",
+ "CLIP Input Switch",
+ "CLIP Vision Input Switch",
+ "CLIPSeg Batch Masking",
+ "CLIPSeg Masking",
+ "CLIPSeg Model Loader",
+ "CLIPTextEncode (BlenderNeko Advanced + NSP)",
+ "CLIPTextEncode (NSP)",
+ "Cache Node",
+ "Checkpoint Loader",
+ "Checkpoint Loader (Simple)",
+ "Conditioning Input Switch",
+ "Constant Number",
+ "Control Net Model Input Switch",
+ "Convert Masks to Images",
+ "Create Grid Image",
+ "Create Grid Image from Batch",
+ "Create Morph Image",
+ "Create Morph Image from Path",
+ "Create Video from Path",
+ "Debug Number to Console",
+ "Dictionary to Console",
+ "Diffusers Hub Model Down-Loader",
+ "Diffusers Model Loader",
+ "Export API",
+ "Image Analyze",
+ "Image Aspect Ratio",
+ "Image Batch",
+ "Image Blank",
+ "Image Blend",
+ "Image Blend by Mask",
+ "Image Blending Mode",
+ "Image Bloom Filter",
+ "Image Bounds",
+ "Image Bounds to Console",
+ "Image Canny Filter",
+ "Image Chromatic Aberration",
+ "Image Color Palette",
+ "Image Crop Face",
+ "Image Crop Location",
+ "Image Crop Square Location",
+ "Image Displacement Warp",
+ "Image Dragan Photography Filter",
+ "Image Edge Detection Filter",
+ "Image Film Grain",
+ "Image Filter Adjustments",
+ "Image Flip",
+ "Image Generate Gradient",
+ "Image Gradient Map",
+ "Image High Pass Filter",
+ "Image History Loader",
+ "Image Input Switch",
+ "Image Levels Adjustment",
+ "Image Load",
+ "Image Lucy Sharpen",
+ "Image Median Filter",
+ "Image Mix RGB Channels",
+ "Image Monitor Effects Filter",
+ "Image Nova Filter",
+ "Image Padding",
+ "Image Paste Crop",
+ "Image Paste Crop by Location",
+ "Image Paste Face",
+ "Image Perlin Noise",
+ "Image Perlin Power Fractal",
+ "Image Pixelate",
+ "Image Power Noise",
+ "Image Rembg (Remove Background)",
+ "Image Remove Background (Alpha)",
+ "Image Remove Color",
+ "Image Resize",
+ "Image Rotate",
+ "Image Rotate Hue",
+ "Image SSAO (Ambient Occlusion)",
+ "Image SSDO (Direct Occlusion)",
+ "Image Save",
+ "Image Seamless Texture",
+ "Image Select Channel",
+ "Image Select Color",
+ "Image Shadows and Highlights",
+ "Image Size to Number",
+ "Image Stitch",
+ "Image Style Filter",
+ "Image Threshold",
+ "Image Tiled",
+ "Image Transpose",
+ "Image Voronoi Noise Filter",
+ "Image fDOF Filter",
+ "Image to Latent Mask",
+ "Image to Noise",
+ "Image to Seed",
+ "Images to Linear",
+ "Images to RGB",
+ "Inset Image Bounds",
+ "Integer place counter",
+ "KSampler (WAS)",
+ "KSampler Cycle",
+ "Latent Batch",
+ "Latent Input Switch",
+ "Latent Noise Injection",
+ "Latent Size to Number",
+ "Latent Upscale by Factor (WAS)",
+ "Load Cache",
+ "Load Image Batch",
+ "Load Lora",
+ "Load Text File",
+ "Logic Boolean",
+ "Logic Boolean Primitive",
+ "Logic Comparison AND",
+ "Logic Comparison OR",
+ "Logic Comparison XOR",
+ "Logic NOT",
+ "Lora Input Switch",
+ "Lora Loader",
+ "Mask Arbitrary Region",
+ "Mask Batch",
+ "Mask Batch to Mask",
+ "Mask Ceiling Region",
+ "Mask Crop Dominant Region",
+ "Mask Crop Minority Region",
+ "Mask Crop Region",
+ "Mask Dilate Region",
+ "Mask Dominant Region",
+ "Mask Erode Region",
+ "Mask Fill Holes",
+ "Mask Floor Region",
+ "Mask Gaussian Region",
+ "Mask Invert",
+ "Mask Minority Region",
+ "Mask Paste Region",
+ "Mask Smooth Region",
+ "Mask Threshold Region",
+ "Masks Add",
+ "Masks Combine Batch",
+ "Masks Combine Regions",
+ "Masks Subtract",
+ "MiDaS Depth Approximation",
+ "MiDaS Mask Image",
+ "MiDaS Model Loader",
+ "Model Input Switch",
+ "Number Counter",
+ "Number Input Condition",
+ "Number Input Switch",
+ "Number Multiple Of",
+ "Number Operation",
+ "Number PI",
+ "Number to Float",
+ "Number to Int",
+ "Number to Seed",
+ "Number to String",
+ "Number to Text",
+ "Prompt Multiple Styles Selector",
+ "Prompt Styles Selector",
+ "Random Number",
+ "SAM Image Mask",
+ "SAM Model Loader",
+ "SAM Parameters",
+ "SAM Parameters Combine",
+ "Samples Passthrough (Stat System)",
+ "Save Text File",
+ "Seed",
+ "String to Text",
+ "Tensor Batch to Image",
+ "Text Add Token by Input",
+ "Text Add Tokens",
+ "Text Compare",
+ "Text Concatenate",
+ "Text Contains",
+ "Text Dictionary Convert",
+ "Text Dictionary Get",
+ "Text Dictionary Keys",
+ "Text Dictionary New",
+ "Text Dictionary To Text",
+ "Text Dictionary Update",
+ "Text File History Loader",
+ "Text Find and Replace",
+ "Text Find and Replace Input",
+ "Text Find and Replace by Dictionary",
+ "Text Input Switch",
+ "Text List",
+ "Text List Concatenate",
+ "Text List to Text",
+ "Text Load Line From File",
+ "Text Multiline",
+ "Text Parse A1111 Embeddings",
+ "Text Parse Noodle Soup Prompts",
+ "Text Parse Tokens",
+ "Text Random Line",
+ "Text Random Prompt",
+ "Text Shuffle",
+ "Text String",
+ "Text String Truncate",
+ "Text to Conditioning",
+ "Text to Console",
+ "Text to Number",
+ "Text to String",
+ "True Random.org Number Generator",
+ "Upscale Model Loader",
+ "Upscale Model Switch",
+ "VAE Input Switch",
+ "Video Dump Frames",
+ "Write to GIF",
+ "Write to Video",
+ "unCLIP Checkpoint Loader"
+ ],
+ {
+ "title_aux": "WAS Node Suite"
+ }
+ ],
+ "https://github.com/WebDev9000/WebDev9000-Nodes": [
+ [
+ "IgnoreBraces",
+ "SettingsSwitch"
+ ],
+ {
+ "title_aux": "WebDev9000-Nodes"
+ }
+ ],
+ "https://github.com/YMC-GitHub/ymc-node-suite-comfyui": [
+ [
+ "canvas-util-cal-size",
+ "conditioning-util-input-switch",
+ "cutoff-region-util",
+ "hks-util-cal-denoise-step",
+ "img-util-get-image-size",
+ "img-util-switch-input-image",
+ "io-image-save",
+ "io-text-save",
+ "io-util-file-list-get",
+ "io-util-file-list-get-text",
+ "number-util-random-num",
+ "pipe-util-to-basic-pipe",
+ "region-util-get-by-center-and-size",
+ "region-util-get-by-lt",
+ "region-util-get-crop-location-from-center-size-text",
+ "region-util-get-pad-out-location-by-size",
+ "text-preset-colors",
+ "text-util-join-text",
+ "text-util-loop-text",
+ "text-util-path-list",
+ "text-util-prompt-add-prompt",
+ "text-util-prompt-adv-dup",
+ "text-util-prompt-adv-search",
+ "text-util-prompt-del",
+ "text-util-prompt-dup",
+ "text-util-prompt-join",
+ "text-util-prompt-search",
+ "text-util-prompt-shuffle",
+ "text-util-prompt-std",
+ "text-util-prompt-unweight",
+ "text-util-random-text",
+ "text-util-search-text",
+ "text-util-show-text",
+ "text-util-switch-text",
+ "xyz-util-txt-to-int"
+ ],
+ {
+ "title_aux": "ymc-node-suite-comfyui"
+ }
+ ],
+ "https://github.com/YOUR-WORST-TACO/ComfyUI-TacoNodes": [
+ [
+ "Example",
+ "TacoAnimatedLoader",
+ "TacoGifMaker",
+ "TacoImg2ImgAnimatedLoader",
+ "TacoImg2ImgAnimatedProcessor",
+ "TacoLatent"
+ ],
+ {
+ "title_aux": "ComfyUI-TacoNodes"
+ }
+ ],
+ "https://github.com/YinBailiang/MergeBlockWeighted_fo_ComfyUI": [
+ [
+ "MergeBlockWeighted"
+ ],
+ {
+ "title_aux": "MergeBlockWeighted_fo_ComfyUI"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-ArtGallery": [
+ [
+ "ArtGallery_Zho",
+ "ArtistsImage_Zho",
+ "CamerasImage_Zho",
+ "FilmsImage_Zho",
+ "MovementsImage_Zho",
+ "StylesImage_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI-ArtGallery"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Gemini": [
+ [
+ "ConcatText_Zho",
+ "DisplayText_Zho",
+ "Gemini_API_Chat_Zho",
+ "Gemini_API_S_Chat_Zho",
+ "Gemini_API_S_Vsion_ImgURL_Zho",
+ "Gemini_API_S_Zho",
+ "Gemini_API_Vsion_ImgURL_Zho",
+ "Gemini_API_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI-Gemini"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-InstantID": [
+ [
+ "IDBaseModelLoader_fromhub",
+ "IDBaseModelLoader_local",
+ "IDControlNetLoader",
+ "IDGenerationNode",
+ "ID_Prompt_Styler",
+ "InsightFaceLoader_Zho",
+ "Ipadapter_instantidLoader"
+ ],
+ {
+ "title_aux": "ComfyUI-InstantID"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-PhotoMaker-ZHO": [
+ [
+ "BaseModel_Loader_fromhub",
+ "BaseModel_Loader_local",
+ "LoRALoader",
+ "NEW_PhotoMaker_Generation",
+ "PhotoMakerAdapter_Loader_fromhub",
+ "PhotoMakerAdapter_Loader_local",
+ "PhotoMaker_Generation",
+ "Prompt_Styler",
+ "Ref_Image_Preprocessing"
+ ],
+ {
+ "title_aux": "ComfyUI PhotoMaker (ZHO)"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Q-Align": [
+ [
+ "QAlign_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI-Q-Align"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-VL-API": [
+ [
+ "QWenVL_API_S_Multi_Zho",
+ "QWenVL_API_S_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI-Qwen-VL-API"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-SVD-ZHO": [
+ [
+ "SVD_Aspect_Ratio_Zho",
+ "SVD_Steps_MotionStrength_Seed_Zho",
+ "SVD_Styler_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI-SVD-ZHO (WIP)"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-SegMoE": [
+ [
+ "SMoE_Generation_Zho",
+ "SMoE_ModelLoader_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI SegMoE"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite": [
+ [
+ "AlphaChanelAddByMask",
+ "ImageCompositeBy_BG_Zho",
+ "ImageCompositeBy_Zho",
+ "ImageComposite_BG_Zho",
+ "ImageComposite_Zho",
+ "RGB_Image_Zho",
+ "Text_Image_Frame_Zho",
+ "Text_Image_Multiline_Zho",
+ "Text_Image_Zho"
+ ],
+ {
+ "title_aux": "ComfyUI-Text_Image-Composite [WIP]"
+ }
+ ],
+ "https://github.com/ZHO-ZHO-ZHO/comfyui-portrait-master-zh-cn": [
+ [
+ "PortraitMaster_\u4e2d\u6587\u7248"
+ ],
+ {
+ "title_aux": "comfyui-portrait-master-zh-cn"
+ }
+ ],
+ "https://github.com/ZaneA/ComfyUI-ImageReward": [
+ [
+ "ImageRewardLoader",
+ "ImageRewardScore"
+ ],
+ {
+ "title_aux": "ImageReward"
+ }
+ ],
+ "https://github.com/Zuellni/ComfyUI-ExLlama": [
+ [
+ "ZuellniExLlamaGenerator",
+ "ZuellniExLlamaLoader",
+ "ZuellniTextPreview",
+ "ZuellniTextReplace"
+ ],
+ {
+ "title_aux": "ComfyUI-ExLlama"
+ }
+ ],
+ "https://github.com/Zuellni/ComfyUI-PickScore-Nodes": [
+ [
+ "ZuellniPickScoreImageProcessor",
+ "ZuellniPickScoreLoader",
+ "ZuellniPickScoreSelector",
+ "ZuellniPickScoreTextProcessor"
+ ],
+ {
+ "title_aux": "ComfyUI PickScore Nodes"
+ }
+ ],
+ "https://github.com/a1lazydog/ComfyUI-AudioScheduler": [
+ [
+ "AmplitudeToGraph",
+ "AmplitudeToNumber",
+ "AudioToAmplitudeGraph",
+ "AudioToFFTs",
+ "BatchAmplitudeSchedule",
+ "ClipAmplitude",
+ "GateNormalizedAmplitude",
+ "LoadAudio",
+ "NormalizeAmplitude",
+ "NormalizedAmplitudeDrivenString",
+ "NormalizedAmplitudeToGraph",
+ "NormalizedAmplitudeToNumber",
+ "TransientAmplitudeBasic"
+ ],
+ {
+ "title_aux": "ComfyUI-AudioScheduler"
+ }
+ ],
+ "https://github.com/abdozmantar/ComfyUI-InstaSwap": [
+ [
+ "InstaSwapFaceSwap",
+ "InstaSwapLoadFaceModel",
+ "InstaSwapSaveFaceModel"
+ ],
+ {
+ "title_aux": "InstaSwap Face Swap Node for ComfyUI"
+ }
+ ],
+ "https://github.com/abyz22/image_control": [
+ [
+ "abyz22_Convertpipe",
+ "abyz22_Editpipe",
+ "abyz22_FirstNonNull",
+ "abyz22_FromBasicPipe_v2",
+ "abyz22_Frompipe",
+ "abyz22_ImpactWildcardEncode",
+ "abyz22_ImpactWildcardEncode_GetPrompt",
+ "abyz22_Ksampler",
+ "abyz22_Padding Image",
+ "abyz22_SaveImage",
+ "abyz22_SetQueue",
+ "abyz22_ToBasicPipe",
+ "abyz22_Topipe",
+ "abyz22_blend_onecolor",
+ "abyz22_blendimages",
+ "abyz22_bypass",
+ "abyz22_drawmask",
+ "abyz22_lamaInpaint",
+ "abyz22_lamaPreprocessor",
+ "abyz22_makecircles",
+ "abyz22_setimageinfo",
+ "abyz22_smallhead"
+ ],
+ {
+ "title_aux": "image_control"
+ }
+ ],
+ "https://github.com/adbrasi/ComfyUI-TrashNodes-DownloadHuggingface": [
+ [
+ "DownloadLinkChecker",
+ "ShowFileNames"
+ ],
+ {
+ "title_aux": "ComfyUI-TrashNodes-DownloadHuggingface"
+ }
+ ],
+ "https://github.com/adieyal/comfyui-dynamicprompts": [
+ [
+ "DPCombinatorialGenerator",
+ "DPFeelingLucky",
+ "DPJinja",
+ "DPMagicPrompt",
+ "DPOutput",
+ "DPRandomGenerator"
+ ],
+ {
+ "title_aux": "DynamicPrompts Custom Nodes"
+ }
+ ],
+ "https://github.com/adriflex/ComfyUI_Blender_Texdiff": [
+ [
+ "ViewportColor",
+ "ViewportDepth"
+ ],
+ {
+ "title_aux": "ComfyUI_Blender_Texdiff"
+ }
+ ],
+ "https://github.com/aegis72/aegisflow_utility_nodes": [
+ [
+ "Add Text To Image",
+ "Aegisflow CLIP Pass",
+ "Aegisflow Conditioning Pass",
+ "Aegisflow Image Pass",
+ "Aegisflow Latent Pass",
+ "Aegisflow Mask Pass",
+ "Aegisflow Model Pass",
+ "Aegisflow Pos/Neg Pass",
+ "Aegisflow SDXL Tuple Pass",
+ "Aegisflow VAE Pass",
+ "Aegisflow controlnet preprocessor bus",
+ "Apply Instagram Filter",
+ "Brightness_Contrast_Ally",
+ "Flatten Colors",
+ "Gaussian Blur_Ally",
+ "GlitchThis Effect",
+ "Hue Rotation",
+ "Image Flip_ally",
+ "Placeholder Tuple",
+ "Swap Color Mode",
+ "aegisflow Multi_Pass",
+ "aegisflow Multi_Pass XL",
+ "af_pipe_in_15",
+ "af_pipe_in_xl",
+ "af_pipe_out_15",
+ "af_pipe_out_xl"
+ ],
+ {
+ "title_aux": "AegisFlow Utility Nodes"
+ }
+ ],
+ "https://github.com/aegis72/comfyui-styles-all": [
+ [
+ "menus"
+ ],
+ {
+ "title_aux": "ComfyUI-styles-all"
+ }
+ ],
+ "https://github.com/ai-liam/comfyui_liam_util": [
+ [
+ "LiamLoadImage"
+ ],
+ {
+ "title_aux": "LiamUtil"
+ }
+ ],
+ "https://github.com/aianimation55/ComfyUI-FatLabels": [
+ [
+ "FatLabels"
+ ],
+ {
+ "title_aux": "Comfy UI FatLabels"
+ }
+ ],
+ "https://github.com/alexopus/ComfyUI-Image-Saver": [
+ [
+ "Cfg Literal (Image Saver)",
+ "Checkpoint Loader with Name (Image Saver)",
+ "Float Literal (Image Saver)",
+ "Image Saver",
+ "Int Literal (Image Saver)",
+ "Sampler Selector (Image Saver)",
+ "Scheduler Selector (Image Saver)",
+ "Seed Generator (Image Saver)",
+ "String Literal (Image Saver)",
+ "Width/Height Literal (Image Saver)"
+ ],
+ {
+ "title_aux": "ComfyUI Image Saver"
+ }
+ ],
+ "https://github.com/alpertunga-bile/prompt-generator-comfyui": [
+ [
+ "Prompt Generator"
+ ],
+ {
+ "title_aux": "prompt-generator"
+ }
+ ],
+ "https://github.com/alsritter/asymmetric-tiling-comfyui": [
+ [
+ "Asymmetric_Tiling_KSampler"
+ ],
+ {
+ "title_aux": "asymmetric-tiling-comfyui"
+ }
+ ],
+ "https://github.com/alt-key-project/comfyui-dream-project": [
+ [
+ "Analyze Palette [Dream]",
+ "Beat Curve [Dream]",
+ "Big Float Switch [Dream]",
+ "Big Image Switch [Dream]",
+ "Big Int Switch [Dream]",
+ "Big Latent Switch [Dream]",
+ "Big Palette Switch [Dream]",
+ "Big Text Switch [Dream]",
+ "Boolean To Float [Dream]",
+ "Boolean To Int [Dream]",
+ "Build Prompt [Dream]",
+ "CSV Curve [Dream]",
+ "CSV Generator [Dream]",
+ "Calculation [Dream]",
+ "Common Frame Dimensions [Dream]",
+ "Compare Palettes [Dream]",
+ "FFMPEG Video Encoder [Dream]",
+ "File Count [Dream]",
+ "Finalize Prompt [Dream]",
+ "Float Input [Dream]",
+ "Float to Log Entry [Dream]",
+ "Frame Count Calculator [Dream]",
+ "Frame Counter (Directory) [Dream]",
+ "Frame Counter (Simple) [Dream]",
+ "Frame Counter Info [Dream]",
+ "Frame Counter Offset [Dream]",
+ "Frame Counter Time Offset [Dream]",
+ "Image Brightness Adjustment [Dream]",
+ "Image Color Shift [Dream]",
+ "Image Contrast Adjustment [Dream]",
+ "Image Motion [Dream]",
+ "Image Sequence Blend [Dream]",
+ "Image Sequence Loader [Dream]",
+ "Image Sequence Saver [Dream]",
+ "Image Sequence Tweening [Dream]",
+ "Int Input [Dream]",
+ "Int to Log Entry [Dream]",
+ "Laboratory [Dream]",
+ "Linear Curve [Dream]",
+ "Log Entry Joiner [Dream]",
+ "Log File [Dream]",
+ "Noise from Area Palettes [Dream]",
+ "Noise from Palette [Dream]",
+ "Palette Color Align [Dream]",
+ "Palette Color Shift [Dream]",
+ "Sample Image Area as Palette [Dream]",
+ "Sample Image as Palette [Dream]",
+ "Saw Curve [Dream]",
+ "Sine Curve [Dream]",
+ "Smooth Event Curve [Dream]",
+ "String Input [Dream]",
+ "String Tokenizer [Dream]",
+ "String to Log Entry [Dream]",
+ "Text Input [Dream]",
+ "Triangle Curve [Dream]",
+ "Triangle Event Curve [Dream]",
+ "WAV Curve [Dream]"
+ ],
+ {
+ "title_aux": "Dream Project Animation Nodes"
+ }
+ ],
+ "https://github.com/alt-key-project/comfyui-dream-video-batches": [
+ [
+ "Blended Transition [DVB]",
+ "Calculation [DVB]",
+ "Create Frame Set [DVB]",
+ "Divide [DVB]",
+ "Fade From Black [DVB]",
+ "Fade To Black [DVB]",
+ "Float Input [DVB]",
+ "For Each Done [DVB]",
+ "For Each Filename [DVB]",
+ "Frame Set Append [DVB]",
+ "Frame Set Frame Dimensions Scaled [DVB]",
+ "Frame Set Index Offset [DVB]",
+ "Frame Set Merger [DVB]",
+ "Frame Set Reindex [DVB]",
+ "Frame Set Repeat [DVB]",
+ "Frame Set Reverse [DVB]",
+ "Frame Set Split Beginning [DVB]",
+ "Frame Set Split End [DVB]",
+ "Frame Set Splitter [DVB]",
+ "Generate Inbetween Frames [DVB]",
+ "Int Input [DVB]",
+ "Linear Camera Pan [DVB]",
+ "Linear Camera Roll [DVB]",
+ "Linear Camera Zoom [DVB]",
+ "Load Image From Path [DVB]",
+ "Multiply [DVB]",
+ "Sine Camera Pan [DVB]",
+ "Sine Camera Roll [DVB]",
+ "Sine Camera Zoom [DVB]",
+ "String Input [DVB]",
+ "Text Input [DVB]",
+ "Trace Memory Allocation [DVB]",
+ "Unwrap Frame Set [DVB]"
+ ],
+ {
+ "title_aux": "Dream Video Batches"
+ }
+ ],
+ "https://github.com/an90ray/ComfyUI_RErouter_CustomNodes": [
+ [
+ "CLIPTextEncode (RE)",
+ "CLIPTextEncodeSDXL (RE)",
+ "CLIPTextEncodeSDXLRefiner (RE)",
+ "Int (RE)",
+ "RErouter <=",
+ "RErouter =>",
+ "String (RE)"
+ ],
+ {
+ "title_aux": "ComfyUI_RErouter_CustomNodes"
+ }
+ ],
+ "https://github.com/andersxa/comfyui-PromptAttention": [
+ [
+ "CLIPAttentionMaskEncode"
+ ],
+ {
+ "title_aux": "CLIP Directional Prompt Attention"
+ }
+ ],
+ "https://github.com/antrobot1234/antrobots-comfyUI-nodepack": [
+ [
+ "composite",
+ "crop",
+ "paste",
+ "preview_mask",
+ "scale"
+ ],
+ {
+ "title_aux": "antrobots ComfyUI Nodepack"
+ }
+ ],
+ "https://github.com/asagi4/ComfyUI-CADS": [
+ [
+ "CADS"
+ ],
+ {
+ "title_aux": "ComfyUI-CADS"
+ }
+ ],
+ "https://github.com/asagi4/comfyui-prompt-control": [
+ [
+ "EditableCLIPEncode",
+ "FilterSchedule",
+ "LoRAScheduler",
+ "PCApplySettings",
+ "PCPromptFromSchedule",
+ "PCScheduleSettings",
+ "PCSplitSampling",
+ "PromptControlSimple",
+ "PromptToSchedule",
+ "ScheduleToCond",
+ "ScheduleToModel"
+ ],
+ {
+ "title_aux": "ComfyUI prompt control"
+ }
+ ],
+ "https://github.com/asagi4/comfyui-utility-nodes": [
+ [
+ "MUForceCacheClear",
+ "MUJinjaRender",
+ "MUSimpleWildcard"
+ ],
+ {
+ "title_aux": "asagi4/comfyui-utility-nodes"
+ }
+ ],
+ "https://github.com/aszc-dev/ComfyUI-CoreMLSuite": [
+ [
+ "Core ML Converter",
+ "Core ML LCM Converter",
+ "Core ML LoRA Loader",
+ "CoreMLModelAdapter",
+ "CoreMLSampler",
+ "CoreMLSamplerAdvanced",
+ "CoreMLUNetLoader"
+ ],
+ {
+ "title_aux": "Core ML Suite for ComfyUI"
+ }
+ ],
+ "https://github.com/avatechai/avatar-graph-comfyui": [
+ [
+ "ApplyMeshTransformAsShapeKey",
+ "B_ENUM",
+ "B_VECTOR3",
+ "B_VECTOR4",
+ "Combine Points",
+ "CreateShapeFlow",
+ "ExportBlendshapes",
+ "ExportGLTF",
+ "Extract Boundary Points",
+ "Image Alpha Mask Merge",
+ "ImageBridge",
+ "LoadImageFromRequest",
+ "LoadImageWithAlpha",
+ "LoadValueFromRequest",
+ "SAM MultiLayer",
+ "Save Image With Workflow"
+ ],
+ {
+ "author": "Avatech Limited",
+ "description": "Include nodes for sam + bpy operation, that allows workflow creations for generative 2d character rig.",
+ "nickname": "Avatar Graph",
+ "title": "Avatar Graph",
+ "title_aux": "avatar-graph-comfyui"
+ }
+ ],
+ "https://github.com/azure-dragon-ai/ComfyUI-ClipScore-Nodes": [
+ [
+ "HaojihuiClipScoreFakeImageProcessor",
+ "HaojihuiClipScoreImageProcessor",
+ "HaojihuiClipScoreImageScore",
+ "HaojihuiClipScoreLoader",
+ "HaojihuiClipScoreRealImageProcessor",
+ "HaojihuiClipScoreTextProcessor"
+ ],
+ {
+ "title_aux": "ComfyUI-ClipScore-Nodes"
+ }
+ ],
+ "https://github.com/badjeff/comfyui_lora_tag_loader": [
+ [
+ "LoraTagLoader"
+ ],
+ {
+ "title_aux": "LoRA Tag Loader for ComfyUI"
+ }
+ ],
+ "https://github.com/banodoco/steerable-motion": [
+ [
+ "BatchCreativeInterpolation"
+ ],
+ {
+ "title_aux": "Steerable Motion"
+ }
+ ],
+ "https://github.com/bash-j/mikey_nodes": [
+ [
+ "AddMetaData",
+ "Batch Crop Image",
+ "Batch Crop Resize Inplace",
+ "Batch Load Images",
+ "Batch Resize Image for SDXL",
+ "Checkpoint Loader Simple Mikey",
+ "CinematicLook",
+ "Empty Latent Ratio Custom SDXL",
+ "Empty Latent Ratio Select SDXL",
+ "EvalFloats",
+ "FaceFixerOpenCV",
+ "FileNamePrefix",
+ "FileNamePrefixDateDirFirst",
+ "Float to String",
+ "HaldCLUT",
+ "Image Caption",
+ "ImageBorder",
+ "ImageOverlay",
+ "ImagePaste",
+ "Int to String",
+ "LMStudioPrompt",
+ "Load Image Based on Number",
+ "LoraSyntaxProcessor",
+ "Mikey Sampler",
+ "Mikey Sampler Base Only",
+ "Mikey Sampler Base Only Advanced",
+ "Mikey Sampler Tiled",
+ "Mikey Sampler Tiled Base Only",
+ "MikeySamplerTiledAdvanced",
+ "MikeySamplerTiledAdvancedBaseOnly",
+ "OobaPrompt",
+ "PresetRatioSelector",
+ "Prompt With SDXL",
+ "Prompt With Style",
+ "Prompt With Style V2",
+ "Prompt With Style V3",
+ "Range Float",
+ "Range Integer",
+ "Ratio Advanced",
+ "Resize Image for SDXL",
+ "Save Image If True",
+ "Save Image With Prompt Data",
+ "Save Images Mikey",
+ "Save Images No Display",
+ "SaveMetaData",
+ "SearchAndReplace",
+ "Seed String",
+ "Style Conditioner",
+ "Style Conditioner Base Only",
+ "Text2InputOr3rdOption",
+ "TextCombinations",
+ "TextCombinations3",
+ "TextConcat",
+ "TextPreserve",
+ "Upscale Tile Calculator",
+ "Wildcard Processor",
+ "WildcardAndLoraSyntaxProcessor",
+ "WildcardOobaPrompt"
+ ],
+ {
+ "title_aux": "Mikey Nodes"
+ }
+ ],
+ "https://github.com/bedovyy/ComfyUI_NAIDGenerator": [
+ [
+ "GenerateNAID",
+ "Img2ImgOptionNAID",
+ "InpaintingOptionNAID",
+ "MaskImageToNAID",
+ "ModelOptionNAID",
+ "PromptToNAID"
+ ],
+ {
+ "title_aux": "ComfyUI_NAIDGenerator"
+ }
+ ],
+ "https://github.com/biegert/ComfyUI-CLIPSeg/raw/main/custom_nodes/clipseg.py": [
+ [
+ "CLIPSeg",
+ "CombineSegMasks"
+ ],
+ {
+ "title_aux": "CLIPSeg"
+ }
+ ],
+ "https://github.com/bilal-arikan/ComfyUI_TextAssets": [
+ [
+ "LoadTextAsset"
+ ],
+ {
+ "title_aux": "ComfyUI_TextAssets"
+ }
+ ],
+ "https://github.com/blepping/ComfyUI-bleh": [
+ [
+ "BlehDeepShrink",
+ "BlehDiscardPenultimateSigma",
+ "BlehHyperTile",
+ "BlehInsaneChainSampler",
+ "BlehModelPatchConditional"
+ ],
+ {
+ "title_aux": "ComfyUI-bleh"
+ }
+ ],
+ "https://github.com/bmad4ever/comfyui_ab_samplercustom": [
+ [
+ "AB SamplerCustom (experimental)"
+ ],
+ {
+ "title_aux": "comfyui_ab_sampler"
+ }
+ ],
+ "https://github.com/bmad4ever/comfyui_bmad_nodes": [
+ [
+ "AdaptiveThresholding",
+ "Add String To Many",
+ "AddAlpha",
+ "AdjustRect",
+ "AnyToAny",
+ "BoundingRect (contours)",
+ "BuildColorRangeAdvanced (hsv)",
+ "BuildColorRangeHSV (hsv)",
+ "CLAHE",
+ "CLIPEncodeMultiple",
+ "CLIPEncodeMultipleAdvanced",
+ "ChameleonMask",
+ "CheckpointLoader (dirty)",
+ "CheckpointLoaderSimple (dirty)",
+ "Color (RGB)",
+ "Color (hexadecimal)",
+ "Color Clip",
+ "Color Clip (advanced)",
+ "Color Clip ADE20k",
+ "ColorDictionary",
+ "ColorDictionary (custom)",
+ "Conditioning (combine multiple)",
+ "Conditioning (combine selective)",
+ "Conditioning Grid (cond)",
+ "Conditioning Grid (string)",
+ "Conditioning Grid (string) Advanced",
+ "Contour To Mask",
+ "Contours",
+ "ControlNetHadamard",
+ "ControlNetHadamard (manual)",
+ "ConvertImg",
+ "CopyMakeBorder",
+ "CreateRequestMetadata",
+ "DistanceTransform",
+ "Draw Contour(s)",
+ "EqualizeHistogram",
+ "ExtendColorList",
+ "ExtendCondList",
+ "ExtendFloatList",
+ "ExtendImageList",
+ "ExtendIntList",
+ "ExtendLatentList",
+ "ExtendMaskList",
+ "ExtendModelList",
+ "ExtendStringList",
+ "FadeMaskEdges",
+ "Filter Contour",
+ "FindComplementaryColor",
+ "FindThreshold",
+ "FlatLatentsIntoSingleGrid",
+ "Framed Mask Grab Cut",
+ "Framed Mask Grab Cut 2",
+ "FromListGet1Color",
+ "FromListGet1Cond",
+ "FromListGet1Float",
+ "FromListGet1Image",
+ "FromListGet1Int",
+ "FromListGet1Latent",
+ "FromListGet1Mask",
+ "FromListGet1Model",
+ "FromListGet1String",
+ "FromListGetColors",
+ "FromListGetConds",
+ "FromListGetFloats",
+ "FromListGetImages",
+ "FromListGetInts",
+ "FromListGetLatents",
+ "FromListGetMasks",
+ "FromListGetModels",
+ "FromListGetStrings",
+ "Get Contour from list",
+ "Get Models",
+ "Get Prompt",
+ "HypernetworkLoader (dirty)",
+ "ImageBatchToList",
+ "InRange (hsv)",
+ "Inpaint",
+ "Input/String to Int Array",
+ "KMeansColor",
+ "Load 64 Encoded Image",
+ "LoraLoader (dirty)",
+ "MaskGrid N KSamplers Advanced",
+ "MaskOuterBlur",
+ "Merge Latent Batch Gridwise",
+ "MonoMerge",
+ "MorphologicOperation",
+ "MorphologicSkeletoning",
+ "NaiveAutoKMeansColor",
+ "OtsuThreshold",
+ "RGB to HSV",
+ "Rect Grab Cut",
+ "Remap",
+ "RemapBarrelDistortion",
+ "RemapFromInsideParabolas",
+ "RemapFromQuadrilateral (homography)",
+ "RemapInsideParabolas",
+ "RemapInsideParabolasAdvanced",
+ "RemapPinch",
+ "RemapReverseBarrelDistortion",
+ "RemapStretch",
+ "RemapToInnerCylinder",
+ "RemapToOuterCylinder",
+ "RemapToQuadrilateral",
+ "RemapWarpPolar",
+ "Repeat Into Grid (image)",
+ "Repeat Into Grid (latent)",
+ "RequestInputs",
+ "SampleColorHSV",
+ "Save Image (api)",
+ "SeamlessClone",
+ "SeamlessClone (simple)",
+ "SetRequestStateToComplete",
+ "String",
+ "String to Float",
+ "String to Integer",
+ "ToColorList",
+ "ToCondList",
+ "ToFloatList",
+ "ToImageList",
+ "ToIntList",
+ "ToLatentList",
+ "ToMaskList",
+ "ToModelList",
+ "ToStringList",
+ "UnGridify (image)",
+ "VAEEncodeBatch"
+ ],
+ {
+ "title_aux": "Bmad Nodes"
+ }
+ ],
+ "https://github.com/bmad4ever/comfyui_lists_cartesian_product": [
+ [
+ "AnyListCartesianProduct"
+ ],
+ {
+ "title_aux": "Lists Cartesian Product"
+ }
+ ],
+ "https://github.com/bradsec/ComfyUI_ResolutionSelector": [
+ [
+ "ResolutionSelector"
+ ],
+ {
+ "title_aux": "ResolutionSelector for ComfyUI"
+ }
+ ],
+ "https://github.com/braintacles/braintacles-comfyui-nodes": [
+ [
+ "CLIPTextEncodeSDXL-Multi-IO",
+ "CLIPTextEncodeSDXL-Pipe",
+ "Empty Latent Image from Aspect-Ratio",
+ "Random Find and Replace",
+ "VAE Decode Pipe",
+ "VAE Decode Tiled Pipe",
+ "VAE Encode Pipe",
+ "VAE Encode Tiled Pipe"
+ ],
+ {
+ "title_aux": "braintacles-nodes"
+ }
+ ],
+ "https://github.com/brianfitzgerald/style_aligned_comfy": [
+ [
+ "StyleAlignedBatchAlign",
+ "StyleAlignedReferenceSampler",
+ "StyleAlignedSampleReferenceLatents"
+ ],
+ {
+ "title_aux": "StyleAligned for ComfyUI"
+ }
+ ],
+ "https://github.com/bronkula/comfyui-fitsize": [
+ [
+ "FS: Crop Image Into Even Pieces",
+ "FS: Fit Image And Resize",
+ "FS: Fit Size From Image",
+ "FS: Fit Size From Int",
+ "FS: Image Region To Mask",
+ "FS: Load Image And Resize To Fit",
+ "FS: Pick Image From Batch",
+ "FS: Pick Image From Batches",
+ "FS: Pick Image From List"
+ ],
+ {
+ "title_aux": "comfyui-fitsize"
+ }
+ ],
+ "https://github.com/bruefire/ComfyUI-SeqImageLoader": [
+ [
+ "VFrame Loader With Mask Editor",
+ "Video Loader With Mask Editor"
+ ],
+ {
+ "title_aux": "ComfyUI Sequential Image Loader"
+ }
+ ],
+ "https://github.com/budihartono/comfyui_otonx_nodes": [
+ [
+ "OTX Integer Multiple Inputs 4",
+ "OTX Integer Multiple Inputs 5",
+ "OTX Integer Multiple Inputs 6",
+ "OTX KSampler Feeder",
+ "OTX Versatile Multiple Inputs 4",
+ "OTX Versatile Multiple Inputs 5",
+ "OTX Versatile Multiple Inputs 6"
+ ],
+ {
+ "title_aux": "Otonx's Custom Nodes"
+ }
+ ],
+ "https://github.com/bvhari/ComfyUI_ImageProcessing": [
+ [
+ "BilateralFilter",
+ "Brightness",
+ "Gamma",
+ "Hue",
+ "Saturation",
+ "SigmoidCorrection",
+ "UnsharpMask"
+ ],
+ {
+ "title_aux": "ImageProcessing"
+ }
+ ],
+ "https://github.com/bvhari/ComfyUI_LatentToRGB": [
+ [
+ "LatentToRGB"
+ ],
+ {
+ "title_aux": "LatentToRGB"
+ }
+ ],
+ "https://github.com/bvhari/ComfyUI_PerpWeight": [
+ [
+ "CLIPTextEncodePerpWeight"
+ ],
+ {
+ "title_aux": "ComfyUI_PerpWeight"
+ }
+ ],
+ "https://github.com/catscandrive/comfyui-imagesubfolders/raw/main/loadImageWithSubfolders.py": [
+ [
+ "LoadImagewithSubfolders"
+ ],
+ {
+ "title_aux": "Image loader with subfolders"
+ }
+ ],
+ "https://github.com/ccvv804/ComfyUI-DiffusersStableCascade-LowVRAM": [
+ [
+ "DiffusersStableCascade"
+ ],
+ {
+ "title_aux": "ComfyUI StableCascade using diffusers for Low VRAM"
+ }
+ ],
+ "https://github.com/celsojr2013/comfyui_simpletools/raw/main/google_translator.py": [
+ [
+ "GoogleTranslator"
+ ],
+ {
+ "title_aux": "ComfyUI SimpleTools Suit"
+ }
+ ],
+ "https://github.com/ceruleandeep/ComfyUI-LLaVA-Captioner": [
+ [
+ "LlavaCaptioner"
+ ],
+ {
+ "title_aux": "ComfyUI LLaVA Captioner"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-DragNUWA": [
+ [
+ "BrushMotion",
+ "CompositeMotionBrush",
+ "CompositeMotionBrushWithoutModel",
+ "DragNUWA Run",
+ "DragNUWA Run MotionBrush",
+ "Get First Image",
+ "Get Last Image",
+ "InstantCameraMotionBrush",
+ "InstantObjectMotionBrush",
+ "Load CheckPoint DragNUWA",
+ "Load MotionBrush From Optical Flow",
+ "Load MotionBrush From Optical Flow Directory",
+ "Load MotionBrush From Optical Flow Without Model",
+ "Load MotionBrush From Tracking Points",
+ "Load MotionBrush From Tracking Points Without Model",
+ "Load Pose KeyPoints",
+ "Loop",
+ "LoopEnd_IMAGE",
+ "LoopStart_IMAGE",
+ "Split Tracking Points"
+ ],
+ {
+ "title_aux": "ComfyUI-DragNUWA"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-DynamiCrafter": [
+ [
+ "DynamiCrafter Simple",
+ "DynamiCrafterLoader"
+ ],
+ {
+ "title_aux": "ComfyUI-DynamiCrafter"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-I2VGEN-XL": [
+ [
+ "I2VGEN-XL Simple",
+ "Modelscope Pipeline Loader"
+ ],
+ {
+ "title_aux": "ComfyUI-I2VGEN-XL"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-LightGlue": [
+ [
+ "LightGlue Loader",
+ "LightGlue Simple",
+ "LightGlue Simple Multi"
+ ],
+ {
+ "title_aux": "ComfyUI-LightGlue"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-Moore-AnimateAnyone": [
+ [
+ "Moore-AnimateAnyone Denoising Unet",
+ "Moore-AnimateAnyone Image Encoder",
+ "Moore-AnimateAnyone Pipeline Loader",
+ "Moore-AnimateAnyone Pose Guider",
+ "Moore-AnimateAnyone Reference Unet",
+ "Moore-AnimateAnyone Simple",
+ "Moore-AnimateAnyone VAE"
+ ],
+ {
+ "title_aux": "ComfyUI-Moore-AnimateAnyone"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-Motion-Vector-Extractor": [
+ [
+ "Motion Vector Extractor",
+ "VideoCombineThenPath"
+ ],
+ {
+ "title_aux": "ComfyUI-Motion-Vector-Extractor"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-MotionCtrl": [
+ [
+ "Load Motion Camera Preset",
+ "Load Motion Traj Preset",
+ "Load Motionctrl Checkpoint",
+ "Motionctrl Cond",
+ "Motionctrl Sample",
+ "Motionctrl Sample Simple",
+ "Select Image Indices"
+ ],
+ {
+ "title_aux": "ComfyUI-MotionCtrl"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-MotionCtrl-SVD": [
+ [
+ "Load Motionctrl-SVD Camera Preset",
+ "Load Motionctrl-SVD Checkpoint",
+ "Motionctrl-SVD Sample Simple"
+ ],
+ {
+ "title_aux": "ComfyUI-MotionCtrl-SVD"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-Panda3d": [
+ [
+ "Panda3dAmbientLight",
+ "Panda3dAttachNewNode",
+ "Panda3dBase",
+ "Panda3dDirectionalLight",
+ "Panda3dLoadDepthModel",
+ "Panda3dLoadModel",
+ "Panda3dLoadTexture",
+ "Panda3dModelMerge",
+ "Panda3dTest",
+ "Panda3dTextureMerge"
+ ],
+ {
+ "title_aux": "ComfyUI-Panda3d"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-Pymunk": [
+ [
+ "PygameRun",
+ "PygameSurface",
+ "PymunkDynamicBox",
+ "PymunkDynamicCircle",
+ "PymunkRun",
+ "PymunkShapeMerge",
+ "PymunkSpace",
+ "PymunkStaticLine"
+ ],
+ {
+ "title_aux": "ComfyUI-Pymunk"
+ }
+ ],
+ "https://github.com/chaojie/ComfyUI-RAFT": [
+ [
+ "Load MotionBrush",
+ "RAFT Run",
+ "Save MotionBrush",
+ "VizMotionBrush"
+ ],
+ {
+ "title_aux": "ComfyUI-RAFT"
+ }
+ ],
+ "https://github.com/chflame163/ComfyUI_LayerStyle": [
+ [
+ "LayerColor: Brightness & Contrast",
+ "LayerColor: ColorAdapter",
+ "LayerColor: Exposure",
+ "LayerColor: Gamma",
+ "LayerColor: HSV",
+ "LayerColor: LAB",
+ "LayerColor: LUT Apply",
+ "LayerColor: RGB",
+ "LayerColor: YUV",
+ "LayerFilter: ChannelShake",
+ "LayerFilter: ColorMap",
+ "LayerFilter: GaussianBlur",
+ "LayerFilter: MotionBlur",
+ "LayerFilter: Sharp & Soft",
+ "LayerFilter: SkinBeauty",
+ "LayerFilter: SoftLight",
+ "LayerFilter: WaterColor",
+ "LayerMask: CreateGradientMask",
+ "LayerMask: MaskBoxDetect",
+ "LayerMask: MaskByDifferent",
+ "LayerMask: MaskEdgeShrink",
+ "LayerMask: MaskEdgeUltraDetail",
+ "LayerMask: MaskGradient",
+ "LayerMask: MaskGrow",
+ "LayerMask: MaskInvert",
+ "LayerMask: MaskMotionBlur",
+ "LayerMask: MaskPreview",
+ "LayerMask: MaskStroke",
+ "LayerMask: PixelSpread",
+ "LayerMask: RemBgUltra",
+ "LayerMask: SegmentAnythingUltra",
+ "LayerStyle: ColorOverlay",
+ "LayerStyle: DropShadow",
+ "LayerStyle: GradientOverlay",
+ "LayerStyle: InnerGlow",
+ "LayerStyle: InnerShadow",
+ "LayerStyle: OuterGlow",
+ "LayerStyle: Stroke",
+ "LayerUtility: ColorImage",
+ "LayerUtility: ColorPicker",
+ "LayerUtility: CropByMask",
+ "LayerUtility: ExtendCanvas",
+ "LayerUtility: GetColorTone",
+ "LayerUtility: GetImageSize",
+ "LayerUtility: GradientImage",
+ "LayerUtility: ImageBlend",
+ "LayerUtility: ImageBlendAdvance",
+ "LayerUtility: ImageChannelMerge",
+ "LayerUtility: ImageChannelSplit",
+ "LayerUtility: ImageMaskScaleAs",
+ "LayerUtility: ImageOpacity",
+ "LayerUtility: ImageScaleRestore",
+ "LayerUtility: ImageShift",
+ "LayerUtility: LayerImageTransform",
+ "LayerUtility: LayerMaskTransform",
+ "LayerUtility: PrintInfo",
+ "LayerUtility: RestoreCropBox",
+ "LayerUtility: TextImage",
+ "LayerUtility: XY to Percent"
+ ],
+ {
+ "title_aux": "ComfyUI Layer Style"
+ }
+ ],
+ "https://github.com/chflame163/ComfyUI_MSSpeech_TTS": [
+ [
+ "Input Trigger",
+ "MicrosoftSpeech_TTS",
+ "Play Sound",
+ "Play Sound (loop)"
+ ],
+ {
+ "title_aux": "ComfyUI_MSSpeech_TTS"
+ }
+ ],
+ "https://github.com/chflame163/ComfyUI_WordCloud": [
+ [
+ "ComfyWordCloud",
+ "LoadTextFile",
+ "RGB_Picker"
+ ],
+ {
+ "title_aux": "ComfyUI_WordCloud"
+ }
+ ],
+ "https://github.com/chibiace/ComfyUI-Chibi-Nodes": [
+ [
+ "ConditionText",
+ "ConditionTextMulti",
+ "ImageAddText",
+ "ImageSimpleResize",
+ "ImageSizeInfo",
+ "ImageTool",
+ "Int2String",
+ "LoadEmbedding",
+ "LoadImageExtended",
+ "Loader",
+ "Prompts",
+ "RandomResolutionLatent",
+ "SaveImages",
+ "SeedGenerator",
+ "SimpleSampler",
+ "TextSplit",
+ "Textbox",
+ "Wildcards"
+ ],
+ {
+ "title_aux": "ComfyUI-Chibi-Nodes"
+ }
+ ],
+ "https://github.com/chrisgoringe/cg-image-picker": [
+ [
+ "Preview Chooser",
+ "Preview Chooser Fabric"
+ ],
+ {
+ "author": "chrisgoringe",
+ "description": "Custom nodes that preview images and pause the workflow to allow the user to select one or more to progress",
+ "nickname": "Image Chooser",
+ "title": "Image Chooser",
+ "title_aux": "Image chooser"
+ }
+ ],
+ "https://github.com/chrisgoringe/cg-noise": [
+ [
+ "Hijack",
+ "KSampler Advanced with Variations",
+ "KSampler with Variations",
+ "UnHijack"
+ ],
+ {
+ "title_aux": "Variation seeds"
+ }
+ ],
+ "https://github.com/chrisgoringe/cg-use-everywhere": [
+ [
+ "Seed Everywhere"
+ ],
+ {
+ "nodename_pattern": "(^(Prompts|Anything) Everywhere|Simple String)",
+ "title_aux": "Use Everywhere (UE Nodes)"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_ColorMod": [
+ [
+ "ColorModEdges",
+ "ColorModPivot",
+ "LoadImageHighPrec",
+ "PreviewImageHighPrec",
+ "SaveImageHighPrec"
+ ],
+ {
+ "title_aux": "ComfyUI_ColorMod"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_DiT": [
+ [
+ "DiTCheckpointLoader",
+ "DiTCheckpointLoaderSimple",
+ "DiTLabelCombine",
+ "DiTLabelSelect",
+ "DiTSampler"
+ ],
+ {
+ "title_aux": "ComfyUI_DiT [WIP]"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_ExtraModels": [
+ [
+ "DiTCondLabelEmpty",
+ "DiTCondLabelSelect",
+ "DitCheckpointLoader",
+ "ExtraVAELoader",
+ "PixArtCheckpointLoader",
+ "PixArtDPMSampler",
+ "PixArtLoraLoader",
+ "PixArtResolutionSelect",
+ "PixArtT5TextEncode",
+ "T5TextEncode",
+ "T5v11Loader"
+ ],
+ {
+ "title_aux": "Extra Models for ComfyUI"
+ }
+ ],
+ "https://github.com/city96/ComfyUI_NetDist": [
+ [
+ "CombineImageBatch",
+ "FetchRemote",
+ "LoadCurrentWorkflowJSON",
+ "LoadDiskWorkflowJSON",
+ "LoadImageUrl",
+ "LoadLatentNumpy",
+ "LoadLatentUrl",
+ "RemoteChainEnd",
+ "RemoteChainStart",
+ "RemoteQueueSimple",
+ "RemoteQueueWorker",
+ "SaveDiskWorkflowJSON",
+ "SaveImageUrl",
+ "SaveLatentNumpy"
+ ],
+ {
+ "title_aux": "ComfyUI_NetDist"
+ }
+ ],
+ "https://github.com/city96/SD-Advanced-Noise": [
+ [
+ "LatentGaussianNoise",
+ "MathEncode"
+ ],
+ {
+ "title_aux": "SD-Advanced-Noise"
+ }
+ ],
+ "https://github.com/city96/SD-Latent-Interposer": [
+ [
+ "LatentInterposer"
+ ],
+ {
+ "title_aux": "Latent-Interposer"
+ }
+ ],
+ "https://github.com/city96/SD-Latent-Upscaler": [
+ [
+ "LatentUpscaler"
+ ],
+ {
+ "title_aux": "SD-Latent-Upscaler"
+ }
+ ],
+ "https://github.com/civitai/comfy-nodes": [
+ [
+ "CivitAI_Checkpoint_Loader",
+ "CivitAI_Lora_Loader"
+ ],
+ {
+ "title_aux": "comfy-nodes"
+ }
+ ],
+ "https://github.com/comfyanonymous/ComfyUI": [
+ [
+ "BasicScheduler",
+ "CLIPLoader",
+ "CLIPMergeSimple",
+ "CLIPSave",
+ "CLIPSetLastLayer",
+ "CLIPTextEncode",
+ "CLIPTextEncodeControlnet",
+ "CLIPTextEncodeSDXL",
+ "CLIPTextEncodeSDXLRefiner",
+ "CLIPVisionEncode",
+ "CLIPVisionLoader",
+ "Canny",
+ "CheckpointLoader",
+ "CheckpointLoaderSimple",
+ "CheckpointSave",
+ "ConditioningAverage",
+ "ConditioningCombine",
+ "ConditioningConcat",
+ "ConditioningSetArea",
+ "ConditioningSetAreaPercentage",
+ "ConditioningSetAreaStrength",
+ "ConditioningSetMask",
+ "ConditioningSetTimestepRange",
+ "ConditioningZeroOut",
+ "ControlNetApply",
+ "ControlNetApplyAdvanced",
+ "ControlNetLoader",
+ "CropMask",
+ "DiffControlNetLoader",
+ "DiffusersLoader",
+ "DualCLIPLoader",
+ "EmptyImage",
+ "EmptyLatentImage",
+ "ExponentialScheduler",
+ "FeatherMask",
+ "FlipSigmas",
+ "FreeU",
+ "FreeU_V2",
+ "GLIGENLoader",
+ "GLIGENTextBoxApply",
+ "GrowMask",
+ "HyperTile",
+ "HypernetworkLoader",
+ "ImageBatch",
+ "ImageBlend",
+ "ImageBlur",
+ "ImageColorToMask",
+ "ImageCompositeMasked",
+ "ImageCrop",
+ "ImageFromBatch",
+ "ImageInvert",
+ "ImageOnlyCheckpointLoader",
+ "ImageOnlyCheckpointSave",
+ "ImagePadForOutpaint",
+ "ImageQuantize",
+ "ImageScale",
+ "ImageScaleBy",
+ "ImageScaleToTotalPixels",
+ "ImageSharpen",
+ "ImageToMask",
+ "ImageUpscaleWithModel",
+ "InpaintModelConditioning",
+ "InvertMask",
+ "JoinImageWithAlpha",
+ "KSampler",
+ "KSamplerAdvanced",
+ "KSamplerSelect",
+ "KarrasScheduler",
+ "LatentAdd",
+ "LatentBatch",
+ "LatentBatchSeedBehavior",
+ "LatentBlend",
+ "LatentComposite",
+ "LatentCompositeMasked",
+ "LatentCrop",
+ "LatentFlip",
+ "LatentFromBatch",
+ "LatentInterpolate",
+ "LatentMultiply",
+ "LatentRotate",
+ "LatentSubtract",
+ "LatentUpscale",
+ "LatentUpscaleBy",
+ "LoadImage",
+ "LoadImageMask",
+ "LoadLatent",
+ "LoraLoader",
+ "LoraLoaderModelOnly",
+ "MaskComposite",
+ "MaskToImage",
+ "ModelMergeAdd",
+ "ModelMergeBlocks",
+ "ModelMergeSimple",
+ "ModelMergeSubtract",
+ "ModelSamplingContinuousEDM",
+ "ModelSamplingDiscrete",
+ "PatchModelAddDownscale",
+ "PerpNeg",
+ "PhotoMakerEncode",
+ "PhotoMakerLoader",
+ "PolyexponentialScheduler",
+ "PorterDuffImageComposite",
+ "PreviewImage",
+ "RebatchImages",
+ "RebatchLatents",
+ "RepeatImageBatch",
+ "RepeatLatentBatch",
+ "RescaleCFG",
+ "SDTurboScheduler",
+ "SD_4XUpscale_Conditioning",
+ "SVD_img2vid_Conditioning",
+ "SamplerCustom",
+ "SamplerDPMPP_2M_SDE",
+ "SamplerDPMPP_SDE",
+ "SaveAnimatedPNG",
+ "SaveAnimatedWEBP",
+ "SaveImage",
+ "SaveLatent",
+ "SelfAttentionGuidance",
+ "SetLatentNoiseMask",
+ "SolidMask",
+ "SplitImageWithAlpha",
+ "SplitSigmas",
+ "StableCascade_EmptyLatentImage",
+ "StableCascade_StageB_Conditioning",
+ "StableZero123_Conditioning",
+ "StableZero123_Conditioning_Batched",
+ "StyleModelApply",
+ "StyleModelLoader",
+ "TomePatchModel",
+ "UNETLoader",
+ "UpscaleModelLoader",
+ "VAEDecode",
+ "VAEDecodeTiled",
+ "VAEEncode",
+ "VAEEncodeForInpaint",
+ "VAEEncodeTiled",
+ "VAELoader",
+ "VAESave",
+ "VPScheduler",
+ "VideoLinearCFGGuidance",
+ "unCLIPCheckpointLoader",
+ "unCLIPConditioning"
+ ],
+ {
+ "title_aux": "ComfyUI"
+ }
+ ],
+ "https://github.com/comfyanonymous/ComfyUI_experiments": [
+ [
+ "ModelMergeBlockNumber",
+ "ModelMergeSDXL",
+ "ModelMergeSDXLDetailedTransformers",
+ "ModelMergeSDXLTransformers",
+ "ModelSamplerTonemapNoiseTest",
+ "ReferenceOnlySimple",
+ "RescaleClassifierFreeGuidanceTest",
+ "TonemapNoiseWithRescaleCFG"
+ ],
+ {
+ "title_aux": "ComfyUI_experiments"
+ }
+ ],
+ "https://github.com/concarne000/ConCarneNode": [
+ [
+ "BingImageGrabber",
+ "Zephyr"
+ ],
+ {
+ "title_aux": "ConCarneNode"
+ }
+ ],
+ "https://github.com/coreyryanhanson/ComfyQR": [
+ [
+ "comfy-qr-by-image-size",
+ "comfy-qr-by-module-size",
+ "comfy-qr-by-module-split",
+ "comfy-qr-mask_errors"
+ ],
+ {
+ "title_aux": "ComfyQR"
+ }
+ ],
+ "https://github.com/coreyryanhanson/ComfyQR-scanning-nodes": [
+ [
+ "comfy-qr-read",
+ "comfy-qr-validate"
+ ],
+ {
+ "title_aux": "ComfyQR-scanning-nodes"
+ }
+ ],
+ "https://github.com/cubiq/ComfyUI_IPAdapter_plus": [
+ [
+ "IPAdapterApply",
+ "IPAdapterApplyEncoded",
+ "IPAdapterApplyFaceID",
+ "IPAdapterBatchEmbeds",
+ "IPAdapterEncoder",
+ "IPAdapterLoadEmbeds",
+ "IPAdapterModelLoader",
+ "IPAdapterSaveEmbeds",
+ "IPAdapterTilesMasked",
+ "InsightFaceLoader",
+ "PrepImageForClipVision",
+ "PrepImageForInsightFace"
+ ],
+ {
+ "title_aux": "ComfyUI_IPAdapter_plus"
+ }
+ ],
+ "https://github.com/cubiq/ComfyUI_InstantID": [
+ [
+ "ApplyInstantID",
+ "FaceKeypointsPreprocessor",
+ "InstantIDFaceAnalysis",
+ "InstantIDModelLoader"
+ ],
+ {
+ "title_aux": "ComfyUI InstantID (Native Support)"
+ }
+ ],
+ "https://github.com/cubiq/ComfyUI_SimpleMath": [
+ [
+ "SimpleMath",
+ "SimpleMathDebug"
+ ],
+ {
+ "title_aux": "Simple Math"
+ }
+ ],
+ "https://github.com/cubiq/ComfyUI_essentials": [
+ [
+ "BatchCount+",
+ "CLIPTextEncodeSDXL+",
+ "ConsoleDebug+",
+ "DebugTensorShape+",
+ "DrawText+",
+ "ExtractKeyframes+",
+ "GetImageSize+",
+ "ImageApplyLUT+",
+ "ImageCASharpening+",
+ "ImageCompositeFromMaskBatch+",
+ "ImageCrop+",
+ "ImageDesaturate+",
+ "ImageEnhanceDifference+",
+ "ImageExpandBatch+",
+ "ImageFlip+",
+ "ImageFromBatch+",
+ "ImagePosterize+",
+ "ImageRemoveBackground+",
+ "ImageResize+",
+ "ImageSeamCarving+",
+ "KSamplerVariationsStochastic+",
+ "KSamplerVariationsWithNoise+",
+ "MaskBatch+",
+ "MaskBlur+",
+ "MaskExpandBatch+",
+ "MaskFlip+",
+ "MaskFromBatch+",
+ "MaskFromColor+",
+ "MaskPreview+",
+ "ModelCompile+",
+ "RemBGSession+",
+ "SDXLEmptyLatentSizePicker+",
+ "SimpleMath+",
+ "TransitionMask+"
+ ],
+ {
+ "title_aux": "ComfyUI Essentials"
+ }
+ ],
+ "https://github.com/dagthomas/comfyui_dagthomas": [
+ [
+ "CSL",
+ "CSVPromptGenerator",
+ "PromptGenerator"
+ ],
+ {
+ "title_aux": "SDXL Auto Prompter"
+ }
+ ],
+ "https://github.com/daniel-lewis-ab/ComfyUI-Llama": [
+ [
+ "Call LLM Advanced",
+ "Call LLM Basic",
+ "LLM_Create_Completion Advanced",
+ "LLM_Detokenize",
+ "LLM_Embed",
+ "LLM_Eval",
+ "LLM_Load_State",
+ "LLM_Reset",
+ "LLM_Sample",
+ "LLM_Save_State",
+ "LLM_Token_BOS",
+ "LLM_Token_EOS",
+ "LLM_Tokenize",
+ "Load LLM Model Advanced",
+ "Load LLM Model Basic"
+ ],
+ {
+ "title_aux": "ComfyUI-Llama"
+ }
+ ],
+ "https://github.com/daniel-lewis-ab/ComfyUI-TTS": [
+ [
+ "Load_Piper_Model",
+ "Piper_Speak_Text"
+ ],
+ {
+ "title_aux": "ComfyUI-TTS"
+ }
+ ],
+ "https://github.com/darkpixel/darkprompts": [
+ [
+ "DarkCombine",
+ "DarkFaceIndexShuffle",
+ "DarkLoRALoader",
+ "DarkPrompt"
+ ],
+ {
+ "title_aux": "DarkPrompts"
+ }
+ ],
+ "https://github.com/davask/ComfyUI-MarasIT-Nodes": [
+ [
+ "MarasitBusNode"
+ ],
+ {
+ "title_aux": "MarasIT Nodes"
+ }
+ ],
+ "https://github.com/dave-palt/comfyui_DSP_imagehelpers": [
+ [
+ "dsp-imagehelpers-concat"
+ ],
+ {
+ "title_aux": "comfyui_DSP_imagehelpers"
+ }
+ ],
+ "https://github.com/dawangraoming/ComfyUI_ksampler_gpu/raw/main/ksampler_gpu.py": [
+ [
+ "KSamplerAdvancedGPU",
+ "KSamplerGPU"
+ ],
+ {
+ "title_aux": "KSampler GPU"
+ }
+ ],
+ "https://github.com/daxthin/DZ-FaceDetailer": [
+ [
+ "DZ_Face_Detailer"
+ ],
+ {
+ "title_aux": "DZ-FaceDetailer"
+ }
+ ],
+ "https://github.com/deroberon/StableZero123-comfyui": [
+ [
+ "SDZero ImageSplit",
+ "Stablezero123",
+ "Stablezero123WithDepth"
+ ],
+ {
+ "title_aux": "StableZero123-comfyui"
+ }
+ ],
+ "https://github.com/deroberon/demofusion-comfyui": [
+ [
+ "Batch Unsampler",
+ "Demofusion",
+ "Demofusion From Single File",
+ "Iterative Mixing KSampler"
+ ],
+ {
+ "title_aux": "demofusion-comfyui"
+ }
+ ],
+ "https://github.com/dfl/comfyui-clip-with-break": [
+ [
+ "AdvancedCLIPTextEncodeWithBreak",
+ "CLIPTextEncodeWithBreak"
+ ],
+ {
+ "author": "dfl",
+ "description": "CLIP text encoder that does BREAK prompting like A1111",
+ "nickname": "CLIP with BREAK",
+ "title": "CLIP with BREAK syntax",
+ "title_aux": "comfyui-clip-with-break"
+ }
+ ],
+ "https://github.com/digitaljohn/comfyui-propost": [
+ [
+ "ProPostApplyLUT",
+ "ProPostDepthMapBlur",
+ "ProPostFilmGrain",
+ "ProPostRadialBlur",
+ "ProPostVignette"
+ ],
+ {
+ "title_aux": "ComfyUI-ProPost"
+ }
+ ],
+ "https://github.com/dimtoneff/ComfyUI-PixelArt-Detector": [
+ [
+ "PixelArtAddDitherPattern",
+ "PixelArtDetectorConverter",
+ "PixelArtDetectorSave",
+ "PixelArtDetectorToImage",
+ "PixelArtLoadPalettes"
+ ],
+ {
+ "title_aux": "ComfyUI PixelArt Detector"
+ }
+ ],
+ "https://github.com/diontimmer/ComfyUI-Vextra-Nodes": [
+ [
+ "Add Text To Image",
+ "Apply Instagram Filter",
+ "Create Solid Color",
+ "Flatten Colors",
+ "Generate Noise Image",
+ "GlitchThis Effect",
+ "Hue Rotation",
+ "Load Picture Index",
+ "Pixel Sort",
+ "Play Sound At Execution",
+ "Prettify Prompt Using distilgpt2",
+ "Swap Color Mode"
+ ],
+ {
+ "title_aux": "ComfyUI-Vextra-Nodes"
+ }
+ ],
+ "https://github.com/djbielejeski/a-person-mask-generator": [
+ [
+ "APersonMaskGenerator"
+ ],
+ {
+ "title_aux": "a-person-mask-generator"
+ }
+ ],
+ "https://github.com/dmarx/ComfyUI-AudioReactive": [
+ [
+ "OpAbs",
+ "OpBandpass",
+ "OpClamp",
+ "OpHarmonic",
+ "OpModulo",
+ "OpNormalize",
+ "OpNovelty",
+ "OpPercussive",
+ "OpPow",
+ "OpPow2",
+ "OpPredominant_pulse",
+ "OpQuantize",
+ "OpRms",
+ "OpSmoosh",
+ "OpSmooth",
+ "OpSqrt",
+ "OpStretch",
+ "OpSustain",
+ "OpThreshold"
+ ],
+ {
+ "title_aux": "ComfyUI-AudioReactive"
+ }
+ ],
+ "https://github.com/dmarx/ComfyUI-Keyframed": [
+ [
+ "Example",
+ "KfAddCurveToPGroup",
+ "KfAddCurveToPGroupx10",
+ "KfApplyCurveToCond",
+ "KfConditioningAdd",
+ "KfConditioningAddx10",
+ "KfCurveConstant",
+ "KfCurveDraw",
+ "KfCurveFromString",
+ "KfCurveFromYAML",
+ "KfCurveInverse",
+ "KfCurveToAcnLatentKeyframe",
+ "KfCurvesAdd",
+ "KfCurvesAddx10",
+ "KfCurvesDivide",
+ "KfCurvesMultiply",
+ "KfCurvesMultiplyx10",
+ "KfCurvesSubtract",
+ "KfDebug_Clip",
+ "KfDebug_Cond",
+ "KfDebug_Curve",
+ "KfDebug_Float",
+ "KfDebug_Image",
+ "KfDebug_Int",
+ "KfDebug_Latent",
+ "KfDebug_Model",
+ "KfDebug_Passthrough",
+ "KfDebug_Segs",
+ "KfDebug_String",
+ "KfDebug_Vae",
+ "KfDrawSchedule",
+ "KfEvaluateCurveAtT",
+ "KfGetCurveFromPGroup",
+ "KfGetScheduleConditionAtTime",
+ "KfGetScheduleConditionSlice",
+ "KfKeyframedCondition",
+ "KfKeyframedConditionWithText",
+ "KfPGroupCurveAdd",
+ "KfPGroupCurveMultiply",
+ "KfPGroupDraw",
+ "KfPGroupProd",
+ "KfPGroupSum",
+ "KfSetCurveLabel",
+ "KfSetKeyframe",
+ "KfSinusoidalAdjustAmplitude",
+ "KfSinusoidalAdjustFrequency",
+ "KfSinusoidalAdjustPhase",
+ "KfSinusoidalAdjustWavelength",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx2",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx3",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx4",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx5",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx6",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx7",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx8",
+ "KfSinusoidalEntangledZeroOneFromFrequencyx9",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx2",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx3",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx4",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx5",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx6",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx7",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx8",
+ "KfSinusoidalEntangledZeroOneFromWavelengthx9",
+ "KfSinusoidalGetAmplitude",
+ "KfSinusoidalGetFrequency",
+ "KfSinusoidalGetPhase",
+ "KfSinusoidalGetWavelength",
+ "KfSinusoidalWithFrequency",
+ "KfSinusoidalWithWavelength"
+ ],
+ {
+ "title_aux": "ComfyUI-Keyframed"
+ }
+ ],
+ "https://github.com/drago87/ComfyUI_Dragos_Nodes": [
+ [
+ "file_padding",
+ "image_info",
+ "lora_loader",
+ "vae_loader"
+ ],
+ {
+ "title_aux": "ComfyUI_Dragos_Nodes"
+ }
+ ],
+ "https://github.com/drustan-hawk/primitive-types": [
+ [
+ "float",
+ "int",
+ "string",
+ "string_multiline"
+ ],
+ {
+ "title_aux": "primitive-types"
+ }
+ ],
+ "https://github.com/ealkanat/comfyui_easy_padding": [
+ [
+ "comfyui-easy-padding"
+ ],
+ {
+ "title_aux": "ComfyUI Easy Padding"
+ }
+ ],
+ "https://github.com/edenartlab/eden_comfy_pipelines": [
+ [
+ "CLIP_Interrogator",
+ "Eden_Bool",
+ "Eden_Compare",
+ "Eden_DebugPrint",
+ "Eden_Float",
+ "Eden_Int",
+ "Eden_String",
+ "Filepicker",
+ "IMG_blender",
+ "IMG_padder",
+ "IMG_scaler",
+ "IMG_unpadder",
+ "If ANY execute A else B",
+ "LatentTypeConversion",
+ "SaveImageAdvanced",
+ "VAEDecode_to_folder"
+ ],
+ {
+ "title_aux": "eden_comfy_pipelines"
+ }
+ ],
+ "https://github.com/evanspearman/ComfyMath": [
+ [
+ "CM_BoolBinaryOperation",
+ "CM_BoolToInt",
+ "CM_BoolUnaryOperation",
+ "CM_BreakoutVec2",
+ "CM_BreakoutVec3",
+ "CM_BreakoutVec4",
+ "CM_ComposeVec2",
+ "CM_ComposeVec3",
+ "CM_ComposeVec4",
+ "CM_FloatBinaryCondition",
+ "CM_FloatBinaryOperation",
+ "CM_FloatToInt",
+ "CM_FloatToNumber",
+ "CM_FloatUnaryCondition",
+ "CM_FloatUnaryOperation",
+ "CM_IntBinaryCondition",
+ "CM_IntBinaryOperation",
+ "CM_IntToBool",
+ "CM_IntToFloat",
+ "CM_IntToNumber",
+ "CM_IntUnaryCondition",
+ "CM_IntUnaryOperation",
+ "CM_NearestSDXLResolution",
+ "CM_NumberBinaryCondition",
+ "CM_NumberBinaryOperation",
+ "CM_NumberToFloat",
+ "CM_NumberToInt",
+ "CM_NumberUnaryCondition",
+ "CM_NumberUnaryOperation",
+ "CM_SDXLResolution",
+ "CM_Vec2BinaryCondition",
+ "CM_Vec2BinaryOperation",
+ "CM_Vec2ScalarOperation",
+ "CM_Vec2ToScalarBinaryOperation",
+ "CM_Vec2ToScalarUnaryOperation",
+ "CM_Vec2UnaryCondition",
+ "CM_Vec2UnaryOperation",
+ "CM_Vec3BinaryCondition",
+ "CM_Vec3BinaryOperation",
+ "CM_Vec3ScalarOperation",
+ "CM_Vec3ToScalarBinaryOperation",
+ "CM_Vec3ToScalarUnaryOperation",
+ "CM_Vec3UnaryCondition",
+ "CM_Vec3UnaryOperation",
+ "CM_Vec4BinaryCondition",
+ "CM_Vec4BinaryOperation",
+ "CM_Vec4ScalarOperation",
+ "CM_Vec4ToScalarBinaryOperation",
+ "CM_Vec4ToScalarUnaryOperation",
+ "CM_Vec4UnaryCondition",
+ "CM_Vec4UnaryOperation"
+ ],
+ {
+ "title_aux": "ComfyMath"
+ }
+ ],
+ "https://github.com/fearnworks/ComfyUI_FearnworksNodes/raw/main/fw_nodes.py": [
+ [
+ "Count Files in Directory (FW)",
+ "Count Tokens (FW)",
+ "Token Count Ranker(FW)",
+ "Trim To Tokens (FW)"
+ ],
+ {
+ "title_aux": "Fearnworks Custom Nodes"
+ }
+ ],
+ "https://github.com/fexli/fexli-util-node-comfyui": [
+ [
+ "FEBCPrompt",
+ "FEBatchGenStringBCDocker",
+ "FEColor2Image",
+ "FEColorOut",
+ "FEDataInsertor",
+ "FEDataPacker",
+ "FEDataUnpacker",
+ "FEDeepClone",
+ "FEDictPacker",
+ "FEDictUnpacker",
+ "FEEncLoraLoader",
+ "FEExtraInfoAdd",
+ "FEGenStringBCDocker",
+ "FEGenStringGPT",
+ "FEImageNoiseGenerate",
+ "FEImagePadForOutpaint",
+ "FEImagePadForOutpaintByImage",
+ "FEOperatorIf",
+ "FEPythonStrOp",
+ "FERandomLoraSelect",
+ "FERandomPrompt",
+ "FERandomizedColor2Image",
+ "FERandomizedColorOut",
+ "FERerouteWithName",
+ "FESaveEncryptImage",
+ "FETextCombine",
+ "FETextInput"
+ ],
+ {
+ "title_aux": "fexli-util-node-comfyui"
+ }
+ ],
+ "https://github.com/filipemeneses/comfy_pixelization": [
+ [
+ "Pixelization"
+ ],
+ {
+ "title_aux": "Pixelization"
+ }
+ ],
+ "https://github.com/filliptm/ComfyUI_Fill-Nodes": [
+ [
+ "FL_ImageCaptionSaver",
+ "FL_ImageRandomizer"
+ ],
+ {
+ "title_aux": "ComfyUI_Fill-Nodes"
+ }
+ ],
+ "https://github.com/fitCorder/fcSuite/raw/main/fcSuite.py": [
+ [
+ "fcFloat",
+ "fcFloatMatic",
+ "fcHex",
+ "fcInteger"
+ ],
+ {
+ "title_aux": "fcSuite"
+ }
+ ],
+ "https://github.com/florestefano1975/comfyui-portrait-master": [
+ [
+ "PortraitMaster"
+ ],
+ {
+ "title_aux": "comfyui-portrait-master"
+ }
+ ],
+ "https://github.com/florestefano1975/comfyui-prompt-composer": [
+ [
+ "PromptComposerCustomLists",
+ "PromptComposerEffect",
+ "PromptComposerGrouping",
+ "PromptComposerMerge",
+ "PromptComposerStyler",
+ "PromptComposerTextSingle",
+ "promptComposerTextMultiple"
+ ],
+ {
+ "title_aux": "comfyui-prompt-composer"
+ }
+ ],
+ "https://github.com/flowtyone/ComfyUI-Flowty-LDSR": [
+ [
+ "LDSRModelLoader",
+ "LDSRUpscale",
+ "LDSRUpscaler"
+ ],
+ {
+ "title_aux": "ComfyUI-Flowty-LDSR"
+ }
+ ],
+ "https://github.com/flyingshutter/As_ComfyUI_CustomNodes": [
+ [
+ "BatchIndex_AS",
+ "CropImage_AS",
+ "ImageMixMasked_As",
+ "ImageToMask_AS",
+ "Increment_AS",
+ "Int2Any_AS",
+ "LatentAdd_AS",
+ "LatentMixMasked_As",
+ "LatentMix_AS",
+ "LatentToImages_AS",
+ "LoadLatent_AS",
+ "MapRange_AS",
+ "MaskToImage_AS",
+ "Math_AS",
+ "NoiseImage_AS",
+ "Number2Float_AS",
+ "Number2Int_AS",
+ "Number_AS",
+ "SaveLatent_AS",
+ "TextToImage_AS",
+ "TextWildcardList_AS"
+ ],
+ {
+ "title_aux": "As_ComfyUI_CustomNodes"
+ }
+ ],
+ "https://github.com/foxtrot-roger/comfyui-rf-nodes": [
+ [
+ "LogBool",
+ "LogFloat",
+ "LogInt",
+ "LogNumber",
+ "LogString",
+ "LogVec2",
+ "LogVec3",
+ "RF_AtIndexString",
+ "RF_BoolToString",
+ "RF_FloatToString",
+ "RF_IntToString",
+ "RF_JsonStyleLoader",
+ "RF_MergeLines",
+ "RF_NumberToString",
+ "RF_OptionsString",
+ "RF_RangeFloat",
+ "RF_RangeInt",
+ "RF_RangeNumber",
+ "RF_SavePromptInfo",
+ "RF_SplitLines",
+ "RF_TextConcatenate",
+ "RF_TextInput",
+ "RF_TextReplace",
+ "RF_Timestamp",
+ "RF_ToString",
+ "RF_Vec2ToString",
+ "RF_Vec3ToString",
+ "TextLine"
+ ],
+ {
+ "title_aux": "RF Nodes"
+ }
+ ],
+ "https://github.com/gemell1/ComfyUI_GMIC": [
+ [
+ "GmicCliWrapper"
+ ],
+ {
+ "title_aux": "ComfyUI_GMIC"
+ }
+ ],
+ "https://github.com/giriss/comfy-image-saver": [
+ [
+ "Cfg Literal",
+ "Checkpoint Selector",
+ "Int Literal",
+ "Sampler Selector",
+ "Save Image w/Metadata",
+ "Scheduler Selector",
+ "Seed Generator",
+ "String Literal",
+ "Width/Height Literal"
+ ],
+ {
+ "title_aux": "Save Image with Generation Metadata"
+ }
+ ],
+ "https://github.com/glibsonoran/Plush-for-ComfyUI": [
+ [
+ "DalleImage",
+ "Enhancer",
+ "ImgTextSwitch",
+ "Plush-Exif Wrangler",
+ "mulTextSwitch"
+ ],
+ {
+ "title_aux": "Plush-for-ComfyUI"
+ }
+ ],
+ "https://github.com/glifxyz/ComfyUI-GlifNodes": [
+ [
+ "GlifConsistencyDecoder",
+ "GlifPatchConsistencyDecoderTiled",
+ "SDXLAspectRatio"
+ ],
+ {
+ "title_aux": "ComfyUI-GlifNodes"
+ }
+ ],
+ "https://github.com/glowcone/comfyui-base64-to-image": [
+ [
+ "LoadImageFromBase64"
+ ],
+ {
+ "title_aux": "Load Image From Base64 URI"
+ }
+ ],
+ "https://github.com/godspede/ComfyUI_Substring": [
+ [
+ "SubstringTheory"
+ ],
+ {
+ "title_aux": "ComfyUI Substring"
+ }
+ ],
+ "https://github.com/gokayfem/ComfyUI_VLM_nodes": [
+ [
+ "Joytag",
+ "JsonToText",
+ "KeywordExtraction",
+ "LLMLoader",
+ "LLMPromptGenerator",
+ "LLMSampler",
+ "LLava Loader Simple",
+ "LLavaPromptGenerator",
+ "LLavaSamplerAdvanced",
+ "LLavaSamplerSimple",
+ "LlavaClipLoader",
+ "MoonDream",
+ "PromptGenerateAPI",
+ "SimpleText",
+ "Suggester",
+ "ViewText"
+ ],
+ {
+ "title_aux": "VLM_nodes"
+ }
+ ],
+ "https://github.com/guoyk93/yk-node-suite-comfyui": [
+ [
+ "YKImagePadForOutpaint",
+ "YKMaskToImage"
+ ],
+ {
+ "title_aux": "y.k.'s ComfyUI node suite"
+ }
+ ],
+ "https://github.com/hhhzzyang/Comfyui_Lama": [
+ [
+ "LamaApply",
+ "LamaModelLoader",
+ "YamlConfigLoader"
+ ],
+ {
+ "title_aux": "Comfyui-Lama"
+ }
+ ],
+ "https://github.com/hinablue/ComfyUI_3dPoseEditor": [
+ [
+ "Hina.PoseEditor3D"
+ ],
+ {
+ "title_aux": "ComfyUI 3D Pose Editor"
+ }
+ ],
+ "https://github.com/hustille/ComfyUI_Fooocus_KSampler": [
+ [
+ "KSampler With Refiner (Fooocus)"
+ ],
+ {
+ "title_aux": "ComfyUI_Fooocus_KSampler"
+ }
+ ],
+ "https://github.com/hustille/ComfyUI_hus_utils": [
+ [
+ "3way Prompt Styler",
+ "Batch State",
+ "Date Time Format",
+ "Debug Extra",
+ "Fetch widget value",
+ "Text Hash"
+ ],
+ {
+ "title_aux": "hus' utils for ComfyUI"
+ }
+ ],
+ "https://github.com/hylarucoder/ComfyUI-Eagle-PNGInfo": [
+ [
+ "EagleImageNode",
+ "SDXLPromptStyler",
+ "SDXLPromptStylerAdvanced",
+ "SDXLResolutionPresets"
+ ],
+ {
+ "title_aux": "Eagle PNGInfo"
+ }
+ ],
+ "https://github.com/idrirap/ComfyUI-Lora-Auto-Trigger-Words": [
+ [
+ "FusionText",
+ "LoraListNames",
+ "LoraLoaderAdvanced",
+ "LoraLoaderStackedAdvanced",
+ "LoraLoaderStackedVanilla",
+ "LoraLoaderVanilla",
+ "LoraTagsOnly",
+ "Randomizer",
+ "TagsFormater",
+ "TagsSelector",
+ "TextInputBasic"
+ ],
+ {
+ "title_aux": "ComfyUI-Lora-Auto-Trigger-Words"
+ }
+ ],
+ "https://github.com/imb101/ComfyUI-FaceSwap": [
+ [
+ "FaceSwapNode"
+ ],
+ {
+ "title_aux": "FaceSwap"
+ }
+ ],
+ "https://github.com/jags111/ComfyUI_Jags_Audiotools": [
+ [
+ "BatchJoinAudio",
+ "BatchToList",
+ "BitCrushAudioFX",
+ "BulkVariation",
+ "ChorusAudioFX",
+ "ClippingAudioFX",
+ "CompressorAudioFX",
+ "ConcatAudioList",
+ "ConvolutionAudioFX",
+ "CutAudio",
+ "DelayAudioFX",
+ "DistortionAudioFX",
+ "DuplicateAudio",
+ "GainAudioFX",
+ "GenerateAudioSample",
+ "GenerateAudioWave",
+ "GetAudioFromFolderIndex",
+ "GetSingle",
+ "GetStringByIndex",
+ "HighShelfFilter",
+ "HighpassFilter",
+ "ImageToSpectral",
+ "InvertAudioFX",
+ "JoinAudio",
+ "LadderFilter",
+ "LimiterAudioFX",
+ "ListToBatch",
+ "LoadAudioDir",
+ "LoadAudioFile",
+ "LoadAudioModel (DD)",
+ "LoadVST3",
+ "LowShelfFilter",
+ "LowpassFilter",
+ "MP3CompressorAudioFX",
+ "MixAudioTensors",
+ "NoiseGateAudioFX",
+ "OTTAudioFX",
+ "PeakFilter",
+ "PhaserEffectAudioFX",
+ "PitchShiftAudioFX",
+ "PlotSpectrogram",
+ "PreviewAudioFile",
+ "PreviewAudioTensor",
+ "ResampleAudio",
+ "ReverbAudioFX",
+ "ReverseAudio",
+ "SaveAudioTensor",
+ "SequenceVariation",
+ "SliceAudio",
+ "SoundPlayer",
+ "StretchAudio",
+ "samplerate"
+ ],
+ {
+ "author": "jags111",
+ "description": "This extension offers various audio generation tools",
+ "nickname": "Audiotools",
+ "title": "Jags_Audiotools",
+ "title_aux": "ComfyUI_Jags_Audiotools"
+ }
+ ],
+ "https://github.com/jags111/ComfyUI_Jags_VectorMagic": [
+ [
+ "CircularVAEDecode",
+ "JagsCLIPSeg",
+ "JagsClipseg",
+ "JagsCombineMasks",
+ "SVG",
+ "YoloSEGdetectionNode",
+ "YoloSegNode",
+ "color_drop",
+ "my unique name",
+ "xy_Tiling_KSampler"
+ ],
+ {
+ "author": "jags111",
+ "description": "This extension offers various vector manipulation and generation tools",
+ "nickname": "Jags_VectorMagic",
+ "title": "Jags_VectorMagic",
+ "title_aux": "ComfyUI_Jags_VectorMagic"
+ }
+ ],
+ "https://github.com/jags111/efficiency-nodes-comfyui": [
+ [
+ "AnimateDiff Script",
+ "Apply ControlNet Stack",
+ "Control Net Stacker",
+ "Eff. Loader SDXL",
+ "Efficient Loader",
+ "HighRes-Fix Script",
+ "Image Overlay",
+ "Join XY Inputs of Same Type",
+ "KSampler (Efficient)",
+ "KSampler Adv. (Efficient)",
+ "KSampler SDXL (Eff.)",
+ "LatentUpscaler",
+ "LoRA Stack to String converter",
+ "LoRA Stacker",
+ "Manual XY Entry Info",
+ "NNLatentUpscale",
+ "Noise Control Script",
+ "Pack SDXL Tuple",
+ "Tiled Upscaler Script",
+ "Unpack SDXL Tuple",
+ "XY Input: Add/Return Noise",
+ "XY Input: Aesthetic Score",
+ "XY Input: CFG Scale",
+ "XY Input: Checkpoint",
+ "XY Input: Clip Skip",
+ "XY Input: Control Net",
+ "XY Input: Control Net Plot",
+ "XY Input: Denoise",
+ "XY Input: LoRA",
+ "XY Input: LoRA Plot",
+ "XY Input: LoRA Stacks",
+ "XY Input: Manual XY Entry",
+ "XY Input: Prompt S/R",
+ "XY Input: Refiner On/Off",
+ "XY Input: Sampler/Scheduler",
+ "XY Input: Seeds++ Batch",
+ "XY Input: Steps",
+ "XY Input: VAE",
+ "XY Plot"
+ ],
+ {
+ "title_aux": "Efficiency Nodes for ComfyUI Version 2.0+"
+ }
+ ],
+ "https://github.com/jamal-alkharrat/ComfyUI_rotate_image": [
+ [
+ "RotateImage"
+ ],
+ {
+ "title_aux": "ComfyUI_rotate_image"
+ }
+ ],
+ "https://github.com/jamesWalker55/comfyui-various": [
+ [],
+ {
+ "nodename_pattern": "^JW",
+ "title_aux": "Various ComfyUI Nodes by Type"
+ }
+ ],
+ "https://github.com/jesenzhang/ComfyUI_StreamDiffusion": [
+ [
+ "StreamDiffusion_Loader",
+ "StreamDiffusion_Sampler"
+ ],
+ {
+ "title_aux": "ComfyUI_StreamDiffusion"
+ }
+ ],
+ "https://github.com/jitcoder/lora-info": [
+ [
+ "ImageFromURL",
+ "LoraInfo"
+ ],
+ {
+ "title_aux": "LoraInfo"
+ }
+ ],
+ "https://github.com/jjkramhoeft/ComfyUI-Jjk-Nodes": [
+ [
+ "JjkConcat",
+ "JjkShowText",
+ "JjkText",
+ "SDXLRecommendedImageSize"
+ ],
+ {
+ "title_aux": "ComfyUI-Jjk-Nodes"
+ }
+ ],
+ "https://github.com/jojkaart/ComfyUI-sampler-lcm-alternative": [
+ [
+ "LCMScheduler",
+ "SamplerLCMAlternative",
+ "SamplerLCMCycle"
+ ],
+ {
+ "title_aux": "ComfyUI-sampler-lcm-alternative"
+ }
+ ],
+ "https://github.com/jordoh/ComfyUI-Deepface": [
+ [
+ "DeepfaceExtractFaces",
+ "DeepfaceVerify"
+ ],
+ {
+ "title_aux": "ComfyUI Deepface"
+ }
+ ],
+ "https://github.com/jtrue/ComfyUI-JaRue": [
+ [
+ "Text2Image_jru",
+ "YouTube2Prompt_jru"
+ ],
+ {
+ "nodename_pattern": "_jru$",
+ "title_aux": "ComfyUI-JaRue"
+ }
+ ],
+ "https://github.com/ka-puna/comfyui-yanc": [
+ [
+ "YANC.ConcatStrings",
+ "YANC.FormatDatetimeString",
+ "YANC.GetWidgetValueString",
+ "YANC.IntegerCaster",
+ "YANC.MultilineString",
+ "YANC.TruncateString"
+ ],
+ {
+ "title_aux": "comfyui-yanc"
+ }
+ ],
+ "https://github.com/kadirnar/ComfyUI-Transformers": [
+ [
+ "DepthEstimationPipeline",
+ "ImageClassificationPipeline",
+ "ImageSegmentationPipeline",
+ "ObjectDetectionPipeline"
+ ],
+ {
+ "title_aux": "ComfyUI-Transformers"
+ }
+ ],
+ "https://github.com/kenjiqq/qq-nodes-comfyui": [
+ [
+ "Any List",
+ "Axis Pack",
+ "Axis Unpack",
+ "Image Accumulator End",
+ "Image Accumulator Start",
+ "Load Lines From Text File",
+ "Slice List",
+ "Text Splitter",
+ "XY Grid Helper"
+ ],
+ {
+ "title_aux": "qq-nodes-comfyui"
+ }
+ ],
+ "https://github.com/kft334/Knodes": [
+ [
+ "Image(s) To Websocket (Base64)",
+ "ImageOutput",
+ "Load Image (Base64)",
+ "Load Images (Base64)"
+ ],
+ {
+ "title_aux": "Knodes"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-CCSR": [
+ [
+ "CCSR_Model_Select",
+ "CCSR_Upscale"
+ ],
+ {
+ "title_aux": "ComfyUI-CCSR"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-DDColor": [
+ [
+ "DDColor_Colorize"
+ ],
+ {
+ "title_aux": "ComfyUI-DDColor"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-DiffusersStableCascade": [
+ [
+ "DiffusersStableCascade"
+ ],
+ {
+ "title_aux": "ComfyUI StableCascade using diffusers"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-KJNodes": [
+ [
+ "AddLabel",
+ "BatchCLIPSeg",
+ "BatchCropFromMask",
+ "BatchCropFromMaskAdvanced",
+ "BatchUncrop",
+ "BatchUncropAdvanced",
+ "BboxToInt",
+ "ColorMatch",
+ "ColorToMask",
+ "CondPassThrough",
+ "ConditioningMultiCombine",
+ "ConditioningSetMaskAndCombine",
+ "ConditioningSetMaskAndCombine3",
+ "ConditioningSetMaskAndCombine4",
+ "ConditioningSetMaskAndCombine5",
+ "CreateAudioMask",
+ "CreateFadeMask",
+ "CreateFadeMaskAdvanced",
+ "CreateFluidMask",
+ "CreateGradientMask",
+ "CreateMagicMask",
+ "CreateShapeMask",
+ "CreateTextMask",
+ "CreateVoronoiMask",
+ "CrossFadeImages",
+ "DummyLatentOut",
+ "EmptyLatentImagePresets",
+ "FilterZeroMasksAndCorrespondingImages",
+ "FlipSigmasAdjusted",
+ "FloatConstant",
+ "GLIGENTextBoxApplyBatch",
+ "GenerateNoise",
+ "GetImageRangeFromBatch",
+ "GetImagesFromBatchIndexed",
+ "GetLatentsFromBatchIndexed",
+ "GrowMaskWithBlur",
+ "INTConstant",
+ "ImageBatchRepeatInterleaving",
+ "ImageBatchTestPattern",
+ "ImageConcanate",
+ "ImageGrabPIL",
+ "ImageGridComposite2x2",
+ "ImageGridComposite3x3",
+ "ImageTransformByNormalizedAmplitude",
+ "ImageUpscaleWithModelBatched",
+ "InjectNoiseToLatent",
+ "InsertImageBatchByIndexes",
+ "NormalizeLatent",
+ "NormalizedAmplitudeToMask",
+ "OffsetMask",
+ "OffsetMaskByNormalizedAmplitude",
+ "ReferenceOnlySimple3",
+ "ReplaceImagesInBatch",
+ "ResizeMask",
+ "ReverseImageBatch",
+ "RoundMask",
+ "SaveImageWithAlpha",
+ "ScaleBatchPromptSchedule",
+ "SomethingToString",
+ "SoundReactive",
+ "SplitBboxes",
+ "StableZero123_BatchSchedule",
+ "StringConstant",
+ "VRAM_Debug",
+ "WidgetToString"
+ ],
+ {
+ "title_aux": "KJNodes for ComfyUI"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-Marigold": [
+ [
+ "ColorizeDepthmap",
+ "MarigoldDepthEstimation",
+ "RemapDepth",
+ "SaveImageOpenEXR"
+ ],
+ {
+ "title_aux": "Marigold depth estimation in ComfyUI"
+ }
+ ],
+ "https://github.com/kijai/ComfyUI-SVD": [
+ [
+ "SVDimg2vid"
+ ],
+ {
+ "title_aux": "ComfyUI-SVD"
+ }
+ ],
+ "https://github.com/kinfolk0117/ComfyUI_GradientDeepShrink": [
+ [
+ "GradientPatchModelAddDownscale",
+ "GradientPatchModelAddDownscaleAdvanced"
+ ],
+ {
+ "title_aux": "ComfyUI_GradientDeepShrink"
+ }
+ ],
+ "https://github.com/kinfolk0117/ComfyUI_Pilgram": [
+ [
+ "Pilgram"
+ ],
+ {
+ "title_aux": "ComfyUI_Pilgram"
+ }
+ ],
+ "https://github.com/kinfolk0117/ComfyUI_SimpleTiles": [
+ [
+ "DynamicTileMerge",
+ "DynamicTileSplit",
+ "TileCalc",
+ "TileMerge",
+ "TileSplit"
+ ],
+ {
+ "title_aux": "SimpleTiles"
+ }
+ ],
+ "https://github.com/kinfolk0117/ComfyUI_TiledIPAdapter": [
+ [
+ "TiledIPAdapter"
+ ],
+ {
+ "title_aux": "TiledIPAdapter"
+ }
+ ],
+ "https://github.com/knuknX/ComfyUI-Image-Tools": [
+ [
+ "BatchImagePathLoader",
+ "ImageBgRemoveProcessor",
+ "ImageCheveretoUploader",
+ "ImageStandardResizeProcessor",
+ "JSONMessageNotifyTool",
+ "PreviewJSONNode",
+ "SingleImagePathLoader",
+ "SingleImageUrlLoader"
+ ],
+ {
+ "title_aux": "ComfyUI-Image-Tools"
+ }
+ ],
+ "https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI": [
+ [
+ "LLLiteLoader"
+ ],
+ {
+ "title_aux": "ControlNet-LLLite-ComfyUI"
+ }
+ ],
+ "https://github.com/komojini/ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes": [
+ [
+ "S3 Bucket LoRA",
+ "S3Bucket_Load_LoRA",
+ "XL DreamBooth LoRA",
+ "XLDB_LoRA"
+ ],
+ {
+ "title_aux": "ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes"
+ }
+ ],
+ "https://github.com/komojini/komojini-comfyui-nodes": [
+ [
+ "BatchCreativeInterpolationNodeDynamicSettings",
+ "CachedGetter",
+ "DragNUWAImageCanvas",
+ "FlowBuilder",
+ "FlowBuilder (adv)",
+ "FlowBuilder (advanced)",
+ "FlowBuilder (advanced) Setter",
+ "FlowBuilderSetter",
+ "FlowBuilderSetter (adv)",
+ "Getter",
+ "ImageCropByRatio",
+ "ImageCropByRatioAndResize",
+ "ImageGetter",
+ "ImageMerger",
+ "ImagesCropByRatioAndResizeBatch",
+ "KSamplerAdvancedCacheable",
+ "KSamplerCacheable",
+ "Setter",
+ "UltimateVideoLoader",
+ "UltimateVideoLoader (simple)",
+ "YouTubeVideoLoader"
+ ],
+ {
+ "title_aux": "komojini-comfyui-nodes"
+ }
+ ],
+ "https://github.com/kwaroran/abg-comfyui": [
+ [
+ "Remove Image Background (abg)"
+ ],
+ {
+ "title_aux": "abg-comfyui"
+ }
+ ],
+ "https://github.com/laksjdjf/LCMSampler-ComfyUI": [
+ [
+ "SamplerLCM",
+ "TAESDLoader"
+ ],
+ {
+ "title_aux": "LCMSampler-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/LoRA-Merger-ComfyUI": [
+ [
+ "LoraLoaderFromWeight",
+ "LoraLoaderWeightOnly",
+ "LoraMerge",
+ "LoraSave"
+ ],
+ {
+ "title_aux": "LoRA-Merger-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/attention-couple-ComfyUI": [
+ [
+ "Attention couple"
+ ],
+ {
+ "title_aux": "attention-couple-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/cd-tuner_negpip-ComfyUI": [
+ [
+ "CDTuner",
+ "Negapip",
+ "Negpip"
+ ],
+ {
+ "title_aux": "cd-tuner_negpip-ComfyUI"
+ }
+ ],
+ "https://github.com/laksjdjf/pfg-ComfyUI": [
+ [
+ "PFG"
+ ],
+ {
+ "title_aux": "pfg-ComfyUI"
+ }
+ ],
+ "https://github.com/lilly1987/ComfyUI_node_Lilly": [
+ [
+ "CheckpointLoaderSimpleText",
+ "LoraLoaderText",
+ "LoraLoaderTextRandom",
+ "Random_Sampler",
+ "VAELoaderDecode"
+ ],
+ {
+ "title_aux": "simple wildcard for ComfyUI"
+ }
+ ],
+ "https://github.com/lldacing/comfyui-easyapi-nodes": [
+ [
+ "Base64ToImage",
+ "Base64ToMask",
+ "ImageToBase64",
+ "ImageToBase64Advanced",
+ "LoadImageFromURL",
+ "LoadImageToBase64",
+ "LoadMaskFromURL",
+ "MaskImageToBase64",
+ "MaskToBase64",
+ "MaskToBase64Image",
+ "SamAutoMaskSEGS"
+ ],
+ {
+ "title_aux": "comfyui-easyapi-nodes"
+ }
+ ],
+ "https://github.com/longgui0318/comfyui-mask-util": [
+ [
+ "Mask Region Info",
+ "Mask Selection Of Masks",
+ "Split Masks"
+ ],
+ {
+ "title_aux": "comfyui-mask-util"
+ }
+ ],
+ "https://github.com/lordgasmic/ComfyUI-Wildcards/raw/master/wildcards.py": [
+ [
+ "CLIPTextEncodeWithWildcards"
+ ],
+ {
+ "title_aux": "Wildcards"
+ }
+ ],
+ "https://github.com/lrzjason/ComfyUIJasonNode/raw/main/SDXLMixSampler.py": [
+ [
+ "SDXLMixSampler"
+ ],
+ {
+ "title_aux": "ComfyUIJasonNode"
+ }
+ ],
+ "https://github.com/ltdrdata/ComfyUI-Impact-Pack": [
+ [
+ "AddMask",
+ "BasicPipeToDetailerPipe",
+ "BasicPipeToDetailerPipeSDXL",
+ "BboxDetectorCombined",
+ "BboxDetectorCombined_v2",
+ "BboxDetectorForEach",
+ "BboxDetectorSEGS",
+ "BitwiseAndMask",
+ "BitwiseAndMaskForEach",
+ "CLIPSegDetectorProvider",
+ "CfgScheduleHookProvider",
+ "CombineRegionalPrompts",
+ "CoreMLDetailerHookProvider",
+ "DenoiseScheduleHookProvider",
+ "DenoiseSchedulerDetailerHookProvider",
+ "DetailerForEach",
+ "DetailerForEachDebug",
+ "DetailerForEachDebugPipe",
+ "DetailerForEachPipe",
+ "DetailerForEachPipeForAnimateDiff",
+ "DetailerHookCombine",
+ "DetailerPipeToBasicPipe",
+ "EditBasicPipe",
+ "EditDetailerPipe",
+ "EditDetailerPipeSDXL",
+ "EmptySegs",
+ "FaceDetailer",
+ "FaceDetailerPipe",
+ "FromBasicPipe",
+ "FromBasicPipe_v2",
+ "FromDetailerPipe",
+ "FromDetailerPipeSDXL",
+ "FromDetailerPipe_v2",
+ "ImageListToImageBatch",
+ "ImageMaskSwitch",
+ "ImageReceiver",
+ "ImageSender",
+ "ImpactAssembleSEGS",
+ "ImpactCombineConditionings",
+ "ImpactCompare",
+ "ImpactConcatConditionings",
+ "ImpactConditionalBranch",
+ "ImpactConditionalBranchSelMode",
+ "ImpactConditionalStopIteration",
+ "ImpactControlBridge",
+ "ImpactControlNetApplyAdvancedSEGS",
+ "ImpactControlNetApplySEGS",
+ "ImpactControlNetClearSEGS",
+ "ImpactConvertDataType",
+ "ImpactDecomposeSEGS",
+ "ImpactDilateMask",
+ "ImpactDilateMaskInSEGS",
+ "ImpactDilate_Mask_SEG_ELT",
+ "ImpactDummyInput",
+ "ImpactEdit_SEG_ELT",
+ "ImpactFloat",
+ "ImpactFrom_SEG_ELT",
+ "ImpactGaussianBlurMask",
+ "ImpactGaussianBlurMaskInSEGS",
+ "ImpactHFTransformersClassifierProvider",
+ "ImpactIfNone",
+ "ImpactImageBatchToImageList",
+ "ImpactImageInfo",
+ "ImpactInt",
+ "ImpactInversedSwitch",
+ "ImpactIsNotEmptySEGS",
+ "ImpactKSamplerAdvancedBasicPipe",
+ "ImpactKSamplerBasicPipe",
+ "ImpactLatentInfo",
+ "ImpactLogger",
+ "ImpactLogicalOperators",
+ "ImpactMakeImageBatch",
+ "ImpactMakeImageList",
+ "ImpactMakeTileSEGS",
+ "ImpactMinMax",
+ "ImpactNeg",
+ "ImpactNodeSetMuteState",
+ "ImpactQueueTrigger",
+ "ImpactQueueTriggerCountdown",
+ "ImpactRemoteBoolean",
+ "ImpactRemoteInt",
+ "ImpactSEGSClassify",
+ "ImpactSEGSConcat",
+ "ImpactSEGSLabelFilter",
+ "ImpactSEGSOrderedFilter",
+ "ImpactSEGSPicker",
+ "ImpactSEGSRangeFilter",
+ "ImpactSEGSToMaskBatch",
+ "ImpactSEGSToMaskList",
+ "ImpactScaleBy_BBOX_SEG_ELT",
+ "ImpactSegsAndMask",
+ "ImpactSegsAndMaskForEach",
+ "ImpactSetWidgetValue",
+ "ImpactSimpleDetectorSEGS",
+ "ImpactSimpleDetectorSEGSPipe",
+ "ImpactSimpleDetectorSEGS_for_AD",
+ "ImpactSleep",
+ "ImpactStringSelector",
+ "ImpactSwitch",
+ "ImpactValueReceiver",
+ "ImpactValueSender",
+ "ImpactWildcardEncode",
+ "ImpactWildcardProcessor",
+ "IterativeImageUpscale",
+ "IterativeLatentUpscale",
+ "KSamplerAdvancedProvider",
+ "KSamplerProvider",
+ "LatentPixelScale",
+ "LatentReceiver",
+ "LatentSender",
+ "LatentSwitch",
+ "MMDetDetectorProvider",
+ "MMDetLoader",
+ "MaskDetailerPipe",
+ "MaskListToMaskBatch",
+ "MaskPainter",
+ "MaskToSEGS",
+ "MaskToSEGS_for_AnimateDiff",
+ "MasksToMaskList",
+ "MediaPipeFaceMeshToSEGS",
+ "NoiseInjectionDetailerHookProvider",
+ "NoiseInjectionHookProvider",
+ "ONNXDetectorProvider",
+ "ONNXDetectorSEGS",
+ "PixelKSampleHookCombine",
+ "PixelKSampleUpscalerProvider",
+ "PixelKSampleUpscalerProviderPipe",
+ "PixelTiledKSampleUpscalerProvider",
+ "PixelTiledKSampleUpscalerProviderPipe",
+ "PreviewBridge",
+ "PreviewBridgeLatent",
+ "PreviewDetailerHookProvider",
+ "ReencodeLatent",
+ "ReencodeLatentPipe",
+ "RegionalPrompt",
+ "RegionalSampler",
+ "RegionalSamplerAdvanced",
+ "RemoveImageFromSEGS",
+ "RemoveNoiseMask",
+ "SAMDetectorCombined",
+ "SAMDetectorSegmented",
+ "SAMLoader",
+ "SEGSDetailer",
+ "SEGSDetailerForAnimateDiff",
+ "SEGSLabelFilterDetailerHookProvider",
+ "SEGSOrderedFilterDetailerHookProvider",
+ "SEGSPaste",
+ "SEGSPreview",
+ "SEGSPreviewCNet",
+ "SEGSRangeFilterDetailerHookProvider",
+ "SEGSSwitch",
+ "SEGSToImageList",
+ "SegmDetectorCombined",
+ "SegmDetectorCombined_v2",
+ "SegmDetectorForEach",
+ "SegmDetectorSEGS",
+ "Segs Mask",
+ "Segs Mask ForEach",
+ "SegsMaskCombine",
+ "SegsToCombinedMask",
+ "SetDefaultImageForSEGS",
+ "StepsScheduleHookProvider",
+ "SubtractMask",
+ "SubtractMaskForEach",
+ "TiledKSamplerProvider",
+ "ToBasicPipe",
+ "ToBinaryMask",
+ "ToDetailerPipe",
+ "ToDetailerPipeSDXL",
+ "TwoAdvancedSamplersForMask",
+ "TwoSamplersForMask",
+ "TwoSamplersForMaskUpscalerProvider",
+ "TwoSamplersForMaskUpscalerProviderPipe",
+ "UltralyticsDetectorProvider",
+ "UnsamplerDetailerHookProvider",
+ "UnsamplerHookProvider"
+ ],
+ {
+ "author": "Dr.Lt.Data",
+ "description": "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler.",
+ "nickname": "Impact Pack",
+ "title": "Impact Pack",
+ "title_aux": "ComfyUI Impact Pack"
+ }
+ ],
+ "https://github.com/ltdrdata/ComfyUI-Inspire-Pack": [
+ [
+ "AnimeLineArt_Preprocessor_Provider_for_SEGS //Inspire",
+ "ApplyRegionalIPAdapters //Inspire",
+ "BindImageListPromptList //Inspire",
+ "CLIPTextEncodeWithWeight //Inspire",
+ "CacheBackendData //Inspire",
+ "CacheBackendDataList //Inspire",
+ "CacheBackendDataNumberKey //Inspire",
+ "CacheBackendDataNumberKeyList //Inspire",
+ "Canny_Preprocessor_Provider_for_SEGS //Inspire",
+ "ChangeImageBatchSize //Inspire",
+ "CheckpointLoaderSimpleShared //Inspire",
+ "Color_Preprocessor_Provider_for_SEGS //Inspire",
+ "ConcatConditioningsWithMultiplier //Inspire",
+ "DWPreprocessor_Provider_for_SEGS //Inspire",
+ "FakeScribblePreprocessor_Provider_for_SEGS //Inspire",
+ "FloatRange //Inspire",
+ "FromIPAdapterPipe //Inspire",
+ "GlobalSampler //Inspire",
+ "GlobalSeed //Inspire",
+ "HEDPreprocessor_Provider_for_SEGS //Inspire",
+ "HyperTile //Inspire",
+ "IPAdapterModelHelper //Inspire",
+ "ImageBatchSplitter //Inspire",
+ "InpaintPreprocessor_Provider_for_SEGS //Inspire",
+ "KSampler //Inspire",
+ "KSamplerAdvanced //Inspire",
+ "KSamplerAdvancedPipe //Inspire",
+ "KSamplerAdvancedProgress //Inspire",
+ "KSamplerPipe //Inspire",
+ "KSamplerProgress //Inspire",
+ "LatentBatchSplitter //Inspire",
+ "LeRes_DepthMap_Preprocessor_Provider_for_SEGS //Inspire",
+ "LineArt_Preprocessor_Provider_for_SEGS //Inspire",
+ "ListCounter //Inspire",
+ "LoadImage //Inspire",
+ "LoadImageListFromDir //Inspire",
+ "LoadImagesFromDir //Inspire",
+ "LoadPromptsFromDir //Inspire",
+ "LoadPromptsFromFile //Inspire",
+ "LoadSinglePromptFromFile //Inspire",
+ "LoraBlockInfo //Inspire",
+ "LoraLoaderBlockWeight //Inspire",
+ "MakeBasicPipe //Inspire",
+ "Manga2Anime_LineArt_Preprocessor_Provider_for_SEGS //Inspire",
+ "MediaPipeFaceMeshDetectorProvider //Inspire",
+ "MediaPipe_FaceMesh_Preprocessor_Provider_for_SEGS //Inspire",
+ "MeshGraphormerDepthMapPreprocessorProvider_for_SEGS //Inspire",
+ "MiDaS_DepthMap_Preprocessor_Provider_for_SEGS //Inspire",
+ "OpenPose_Preprocessor_Provider_for_SEGS //Inspire",
+ "PromptBuilder //Inspire",
+ "PromptExtractor //Inspire",
+ "RandomGeneratorForList //Inspire",
+ "RegionalConditioningColorMask //Inspire",
+ "RegionalConditioningSimple //Inspire",
+ "RegionalIPAdapterColorMask //Inspire",
+ "RegionalIPAdapterEncodedColorMask //Inspire",
+ "RegionalIPAdapterEncodedMask //Inspire",
+ "RegionalIPAdapterMask //Inspire",
+ "RegionalPromptColorMask //Inspire",
+ "RegionalPromptSimple //Inspire",
+ "RegionalSeedExplorerColorMask //Inspire",
+ "RegionalSeedExplorerMask //Inspire",
+ "RemoveBackendData //Inspire",
+ "RemoveBackendDataNumberKey //Inspire",
+ "RemoveControlNet //Inspire",
+ "RemoveControlNetFromRegionalPrompts //Inspire",
+ "RetrieveBackendData //Inspire",
+ "RetrieveBackendDataNumberKey //Inspire",
+ "SeedExplorer //Inspire",
+ "ShowCachedInfo //Inspire",
+ "TilePreprocessor_Provider_for_SEGS //Inspire",
+ "ToIPAdapterPipe //Inspire",
+ "UnzipPrompt //Inspire",
+ "WildcardEncode //Inspire",
+ "XY Input: Lora Block Weight //Inspire",
+ "ZipPrompt //Inspire",
+ "Zoe_DepthMap_Preprocessor_Provider_for_SEGS //Inspire"
+ ],
+ {
+ "author": "Dr.Lt.Data",
+ "description": "This extension provides various nodes to support Lora Block Weight and the Impact Pack.",
+ "nickname": "Inspire Pack",
+ "nodename_pattern": "Inspire$",
+ "title": "Inspire Pack",
+ "title_aux": "ComfyUI Inspire Pack"
+ }
+ ],
+ "https://github.com/m-sokes/ComfyUI-Sokes-Nodes": [
+ [
+ "Custom Date Format | sokes \ud83e\uddac",
+ "Latent Switch x9 | sokes \ud83e\uddac"
+ ],
+ {
+ "title_aux": "ComfyUI Sokes Nodes"
+ }
+ ],
+ "https://github.com/m957ymj75urz/ComfyUI-Custom-Nodes/raw/main/clip-text-encode-split/clip_text_encode_split.py": [
+ [
+ "RawText",
+ "RawTextCombine",
+ "RawTextEncode",
+ "RawTextReplace"
+ ],
+ {
+ "title_aux": "m957ymj75urz/ComfyUI-Custom-Nodes"
+ }
+ ],
+ "https://github.com/mape/ComfyUI-mape-Helpers": [
+ [
+ "mape Variable"
+ ],
+ {
+ "author": "mape",
+ "description": "Various QoL improvements like prompt tweaking, variable assignment, image preview, fuzzy search, error reporting, organizing and node navigation.",
+ "nickname": "\ud83d\udfe1 mape's helpers",
+ "title": "mape's helpers",
+ "title_aux": "mape's ComfyUI Helpers"
+ }
+ ],
+ "https://github.com/marhensa/sdxl-recommended-res-calc": [
+ [
+ "RecommendedResCalc"
+ ],
+ {
+ "title_aux": "Recommended Resolution Calculator"
+ }
+ ],
+ "https://github.com/martijnat/comfyui-previewlatent": [
+ [
+ "PreviewLatent",
+ "PreviewLatentAdvanced",
+ "PreviewLatentXL"
+ ],
+ {
+ "title_aux": "comfyui-previewlatent"
+ }
+ ],
+ "https://github.com/massao000/ComfyUI_aspect_ratios": [
+ [
+ "Aspect Ratios Node"
+ ],
+ {
+ "title_aux": "ComfyUI_aspect_ratios"
+ }
+ ],
+ "https://github.com/matan1905/ComfyUI-Serving-Toolkit": [
+ [
+ "DiscordServing",
+ "ServingInputNumber",
+ "ServingInputText",
+ "ServingOutput",
+ "WebSocketServing"
+ ],
+ {
+ "title_aux": "ComfyUI Serving toolkit"
+ }
+ ],
+ "https://github.com/mav-rik/facerestore_cf": [
+ [
+ "CropFace",
+ "FaceRestoreCFWithModel",
+ "FaceRestoreModelLoader"
+ ],
+ {
+ "title_aux": "Facerestore CF (Code Former)"
+ }
+ ],
+ "https://github.com/mbrostami/ComfyUI-HF": [
+ [
+ "GPT2Node"
+ ],
+ {
+ "title_aux": "ComfyUI-HF"
+ }
+ ],
+ "https://github.com/mcmonkeyprojects/sd-dynamic-thresholding": [
+ [
+ "DynamicThresholdingFull",
+ "DynamicThresholdingSimple"
+ ],
+ {
+ "title_aux": "Stable Diffusion Dynamic Thresholding (CFG Scale Fix)"
+ }
+ ],
+ "https://github.com/meap158/ComfyUI-Background-Replacement": [
+ [
+ "BackgroundReplacement",
+ "ImageComposite"
+ ],
+ {
+ "title_aux": "ComfyUI-Background-Replacement"
+ }
+ ],
+ "https://github.com/meap158/ComfyUI-GPU-temperature-protection": [
+ [
+ "GPUTemperatureProtection"
+ ],
+ {
+ "title_aux": "GPU temperature protection"
+ }
+ ],
+ "https://github.com/meap158/ComfyUI-Prompt-Expansion": [
+ [
+ "PromptExpansion"
+ ],
+ {
+ "title_aux": "ComfyUI-Prompt-Expansion"
+ }
+ ],
+ "https://github.com/melMass/comfy_mtb": [
+ [
+ "Animation Builder (mtb)",
+ "Any To String (mtb)",
+ "Batch Float (mtb)",
+ "Batch Float Assemble (mtb)",
+ "Batch Float Fill (mtb)",
+ "Batch Make (mtb)",
+ "Batch Merge (mtb)",
+ "Batch Shake (mtb)",
+ "Batch Shape (mtb)",
+ "Batch Transform (mtb)",
+ "Bbox (mtb)",
+ "Bbox From Mask (mtb)",
+ "Blur (mtb)",
+ "Color Correct (mtb)",
+ "Colored Image (mtb)",
+ "Concat Images (mtb)",
+ "Crop (mtb)",
+ "Debug (mtb)",
+ "Deep Bump (mtb)",
+ "Export With Ffmpeg (mtb)",
+ "Face Swap (mtb)",
+ "Film Interpolation (mtb)",
+ "Fit Number (mtb)",
+ "Float To Number (mtb)",
+ "Get Batch From History (mtb)",
+ "Image Compare (mtb)",
+ "Image Premultiply (mtb)",
+ "Image Remove Background Rembg (mtb)",
+ "Image Resize Factor (mtb)",
+ "Image Tile Offset (mtb)",
+ "Int To Bool (mtb)",
+ "Int To Number (mtb)",
+ "Interpolate Clip Sequential (mtb)",
+ "Latent Lerp (mtb)",
+ "Load Face Analysis Model (mtb)",
+ "Load Face Enhance Model (mtb)",
+ "Load Face Swap Model (mtb)",
+ "Load Film Model (mtb)",
+ "Load Image From Url (mtb)",
+ "Load Image Sequence (mtb)",
+ "Mask To Image (mtb)",
+ "Math Expression (mtb)",
+ "Model Patch Seamless (mtb)",
+ "Pick From Batch (mtb)",
+ "Qr Code (mtb)",
+ "Restore Face (mtb)",
+ "Save Gif (mtb)",
+ "Save Image Grid (mtb)",
+ "Save Image Sequence (mtb)",
+ "Save Tensors (mtb)",
+ "Sharpen (mtb)",
+ "Smart Step (mtb)",
+ "Stack Images (mtb)",
+ "String Replace (mtb)",
+ "Styles Loader (mtb)",
+ "Text To Image (mtb)",
+ "Transform Image (mtb)",
+ "Uncrop (mtb)",
+ "Unsplash Image (mtb)",
+ "Vae Decode (mtb)"
+ ],
+ {
+ "nodename_pattern": "\\(mtb\\)$",
+ "title_aux": "MTB Nodes"
+ }
+ ],
+ "https://github.com/mihaiiancu/ComfyUI_Inpaint": [
+ [
+ "InpaintMediapipe"
+ ],
+ {
+ "title_aux": "mihaiiancu/Inpaint"
+ }
+ ],
+ "https://github.com/mikkel/ComfyUI-text-overlay": [
+ [
+ "Image Text Overlay"
+ ],
+ {
+ "title_aux": "ComfyUI - Text Overlay Plugin"
+ }
+ ],
+ "https://github.com/mikkel/comfyui-mask-boundingbox": [
+ [
+ "Mask Bounding Box"
+ ],
+ {
+ "title_aux": "ComfyUI - Mask Bounding Box"
+ }
+ ],
+ "https://github.com/mlinmg/ComfyUI-LaMA-Preprocessor": [
+ [
+ "LaMaPreprocessor",
+ "lamaPreprocessor"
+ ],
+ {
+ "title_aux": "LaMa Preprocessor [WIP]"
+ }
+ ],
+ "https://github.com/modusCell/ComfyUI-dimension-node-modusCell": [
+ [
+ "DimensionProviderFree modusCell",
+ "DimensionProviderRatio modusCell",
+ "String Concat modusCell"
+ ],
+ {
+ "title_aux": "Preset Dimensions"
+ }
+ ],
+ "https://github.com/mpiquero7164/ComfyUI-SaveImgPrompt": [
+ [
+ "Save IMG Prompt"
+ ],
+ {
+ "title_aux": "SaveImgPrompt"
+ }
+ ],
+ "https://github.com/nagolinc/ComfyUI_FastVAEDecorder_SDXL": [
+ [
+ "FastLatentToImage"
+ ],
+ {
+ "title_aux": "ComfyUI_FastVAEDecorder_SDXL"
+ }
+ ],
+ "https://github.com/natto-maki/ComfyUI-NegiTools": [
+ [
+ "NegiTools_CompositeImages",
+ "NegiTools_DepthEstimationByMarigold",
+ "NegiTools_DetectFaceRotationForInpainting",
+ "NegiTools_ImageProperties",
+ "NegiTools_LatentProperties",
+ "NegiTools_NoiseImageGenerator",
+ "NegiTools_OpenAiDalle3",
+ "NegiTools_OpenAiGpt",
+ "NegiTools_OpenAiGpt4v",
+ "NegiTools_OpenAiTranslate",
+ "NegiTools_OpenPoseToPointList",
+ "NegiTools_PointListToMask",
+ "NegiTools_RandomImageLoader",
+ "NegiTools_SaveImageToDirectory",
+ "NegiTools_SeedGenerator",
+ "NegiTools_StereoImageGenerator",
+ "NegiTools_StringFunction"
+ ],
+ {
+ "title_aux": "ComfyUI-NegiTools"
+ }
+ ],
+ "https://github.com/nicolai256/comfyUI_Nodes_nicolai256/raw/main/yugioh-presets.py": [
+ [
+ "yugioh_Presets"
+ ],
+ {
+ "title_aux": "comfyUI_Nodes_nicolai256"
+ }
+ ],
+ "https://github.com/ningxiaoxiao/comfyui-NDI": [
+ [
+ "NDI_LoadImage",
+ "NDI_SendImage"
+ ],
+ {
+ "title_aux": "comfyui-NDI"
+ }
+ ],
+ "https://github.com/nkchocoai/ComfyUI-PromptUtilities": [
+ [
+ "PromptUtilitiesConstString",
+ "PromptUtilitiesConstStringMultiLine",
+ "PromptUtilitiesFormatString",
+ "PromptUtilitiesJoinStringList",
+ "PromptUtilitiesLoadPreset",
+ "PromptUtilitiesLoadPresetAdvanced",
+ "PromptUtilitiesRandomPreset",
+ "PromptUtilitiesRandomPresetAdvanced"
+ ],
+ {
+ "title_aux": "ComfyUI-PromptUtilities"
+ }
+ ],
+ "https://github.com/nkchocoai/ComfyUI-SizeFromPresets": [
+ [
+ "EmptyLatentImageFromPresetsSD15",
+ "EmptyLatentImageFromPresetsSDXL",
+ "RandomEmptyLatentImageFromPresetsSD15",
+ "RandomEmptyLatentImageFromPresetsSDXL",
+ "RandomSizeFromPresetsSD15",
+ "RandomSizeFromPresetsSDXL",
+ "SizeFromPresetsSD15",
+ "SizeFromPresetsSDXL"
+ ],
+ {
+ "title_aux": "ComfyUI-SizeFromPresets"
+ }
+ ],
+ "https://github.com/nkchocoai/ComfyUI-TextOnSegs": [
+ [
+ "CalcMaxFontSize",
+ "ExtractDominantColor",
+ "GetComplementaryColor",
+ "SegsToRegion",
+ "TextOnSegsFloodFill"
+ ],
+ {
+ "title_aux": "ComfyUI-TextOnSegs"
+ }
+ ],
+ "https://github.com/noembryo/ComfyUI-noEmbryo": [
+ [
+ "PromptTermList1",
+ "PromptTermList2",
+ "PromptTermList3",
+ "PromptTermList4",
+ "PromptTermList5",
+ "PromptTermList6"
+ ],
+ {
+ "author": "noEmbryo",
+ "description": "Some useful nodes for ComfyUI",
+ "nickname": "noEmbryo",
+ "title": "noEmbryo nodes for ComfyUI",
+ "title_aux": "noEmbryo nodes"
+ }
+ ],
+ "https://github.com/nosiu/comfyui-instantId-faceswap": [
+ [
+ "FaceEmbed",
+ "FaceSwapGenerationInpaint",
+ "FaceSwapSetupPipeline",
+ "LCMLora"
+ ],
+ {
+ "title_aux": "ComfyUI InstantID Faceswapper"
+ }
+ ],
+ "https://github.com/noxinias/ComfyUI_NoxinNodes": [
+ [
+ "NoxinChime",
+ "NoxinPromptLoad",
+ "NoxinPromptSave",
+ "NoxinScaledResolution",
+ "NoxinSimpleMath",
+ "NoxinSplitPrompt"
+ ],
+ {
+ "title_aux": "ComfyUI_NoxinNodes"
+ }
+ ],
+ "https://github.com/ntc-ai/ComfyUI-DARE-LoRA-Merge": [
+ [
+ "Apply LoRA",
+ "DARE Merge LoRA Stack",
+ "Save LoRA"
+ ],
+ {
+ "title_aux": "ComfyUI - Apply LoRA Stacker with DARE"
+ }
+ ],
+ "https://github.com/ntdviet/comfyui-ext/raw/main/custom_nodes/gcLatentTunnel/gcLatentTunnel.py": [
+ [
+ "gcLatentTunnel"
+ ],
+ {
+ "title_aux": "ntdviet/comfyui-ext"
+ }
+ ],
+ "https://github.com/omar92/ComfyUI-QualityOfLifeSuit_Omar92": [
+ [
+ "CLIPStringEncode _O",
+ "Chat completion _O",
+ "ChatGPT Simple _O",
+ "ChatGPT _O",
+ "ChatGPT compact _O",
+ "Chat_Completion _O",
+ "Chat_Message _O",
+ "Chat_Message_fromString _O",
+ "Concat Text _O",
+ "ConcatRandomNSP_O",
+ "Debug String _O",
+ "Debug Text _O",
+ "Debug Text route _O",
+ "Edit_image _O",
+ "Equation1param _O",
+ "Equation2params _O",
+ "GetImage_(Width&Height) _O",
+ "GetLatent_(Width&Height) _O",
+ "ImageScaleFactor _O",
+ "ImageScaleFactorSimple _O",
+ "LatentUpscaleFactor _O",
+ "LatentUpscaleFactorSimple _O",
+ "LatentUpscaleMultiply",
+ "Note _O",
+ "RandomNSP _O",
+ "Replace Text _O",
+ "String _O",
+ "Text _O",
+ "Text2Image _O",
+ "Trim Text _O",
+ "VAEDecodeParallel _O",
+ "combine_chat_messages _O",
+ "compine_chat_messages _O",
+ "concat Strings _O",
+ "create image _O",
+ "create_image _O",
+ "debug Completeion _O",
+ "debug messages_O",
+ "float _O",
+ "floatToInt _O",
+ "floatToText _O",
+ "int _O",
+ "intToFloat _O",
+ "load_openAI _O",
+ "replace String _O",
+ "replace String advanced _O",
+ "saveTextToFile _O",
+ "seed _O",
+ "selectLatentFromBatch _O",
+ "string2Image _O",
+ "trim String _O",
+ "variation_image _O"
+ ],
+ {
+ "title_aux": "Quality of life Suit:V2"
+ }
+ ],
+ "https://github.com/ostris/ostris_nodes_comfyui": [
+ [
+ "LLM Pipe Loader - Ostris",
+ "LLM Prompt Upsampling - Ostris",
+ "One Seed - Ostris",
+ "Text Box - Ostris"
+ ],
+ {
+ "nodename_pattern": "- Ostris$",
+ "title_aux": "Ostris Nodes ComfyUI"
+ }
+ ],
+ "https://github.com/ownimage/ComfyUI-ownimage": [
+ [
+ "Caching Image Loader"
+ ],
+ {
+ "title_aux": "ComfyUI-ownimage"
+ }
+ ],
+ "https://github.com/oyvindg/ComfyUI-TrollSuite": [
+ [
+ "BinaryImageMask",
+ "ImagePadding",
+ "LoadLastImage",
+ "RandomMask",
+ "TransparentImage"
+ ],
+ {
+ "title_aux": "ComfyUI-TrollSuite"
+ }
+ ],
+ "https://github.com/palant/extended-saveimage-comfyui": [
+ [
+ "SaveImageExtended"
+ ],
+ {
+ "title_aux": "Extended Save Image for ComfyUI"
+ }
+ ],
+ "https://github.com/palant/image-resize-comfyui": [
+ [
+ "ImageResize"
+ ],
+ {
+ "title_aux": "Image Resize for ComfyUI"
+ }
+ ],
+ "https://github.com/pants007/comfy-pants": [
+ [
+ "CLIPTextEncodeAIO",
+ "Image Make Square"
+ ],
+ {
+ "title_aux": "pants"
+ }
+ ],
+ "https://github.com/paulo-coronado/comfy_clip_blip_node": [
+ [
+ "CLIPTextEncodeBLIP",
+ "CLIPTextEncodeBLIP-2",
+ "Example"
+ ],
+ {
+ "title_aux": "comfy_clip_blip_node"
+ }
+ ],
+ "https://github.com/picturesonpictures/comfy_PoP": [
+ [
+ "AdaptiveCannyDetector_PoP",
+ "AnyAspectRatio",
+ "ConditioningMultiplier_PoP",
+ "ConditioningNormalizer_PoP",
+ "DallE3_PoP",
+ "LoadImageResizer_PoP",
+ "LoraStackLoader10_PoP",
+ "LoraStackLoader_PoP",
+ "VAEDecoderPoP",
+ "VAEEncoderPoP"
+ ],
+ {
+ "title_aux": "comfy_PoP"
+ }
+ ],
+ "https://github.com/pkpkTech/ComfyUI-SaveAVIF": [
+ [
+ "SaveAvif"
+ ],
+ {
+ "title_aux": "ComfyUI-SaveAVIF"
+ }
+ ],
+ "https://github.com/pkpkTech/ComfyUI-TemporaryLoader": [
+ [
+ "LoadTempCheckpoint",
+ "LoadTempLoRA",
+ "LoadTempMultiLoRA"
+ ],
+ {
+ "title_aux": "ComfyUI-TemporaryLoader"
+ }
+ ],
+ "https://github.com/pythongosssss/ComfyUI-Custom-Scripts": [
+ [
+ "CheckpointLoader|pysssss",
+ "ConstrainImageforVideo|pysssss",
+ "ConstrainImage|pysssss",
+ "LoadText|pysssss",
+ "LoraLoader|pysssss",
+ "MathExpression|pysssss",
+ "MultiPrimitive|pysssss",
+ "PlaySound|pysssss",
+ "Repeater|pysssss",
+ "ReroutePrimitive|pysssss",
+ "SaveText|pysssss",
+ "ShowText|pysssss",
+ "StringFunction|pysssss"
+ ],
+ {
+ "title_aux": "pythongosssss/ComfyUI-Custom-Scripts"
+ }
+ ],
+ "https://github.com/pythongosssss/ComfyUI-WD14-Tagger": [
+ [
+ "WD14Tagger|pysssss"
+ ],
+ {
+ "title_aux": "ComfyUI WD 1.4 Tagger"
+ }
+ ],
+ "https://github.com/ramyma/A8R8_ComfyUI_nodes": [
+ [
+ "Base64ImageInput",
+ "Base64ImageOutput"
+ ],
+ {
+ "title_aux": "A8R8 ComfyUI Nodes"
+ }
+ ],
+ "https://github.com/rcfcu2000/zhihuige-nodes-comfyui": [
+ [
+ "Combine ZHGMasks",
+ "Cover ZHGMasks",
+ "From ZHG pip",
+ "GroundingDinoModelLoader (zhihuige)",
+ "GroundingDinoPIPESegment (zhihuige)",
+ "GroundingDinoSAMSegment (zhihuige)",
+ "InvertMask (zhihuige)",
+ "SAMModelLoader (zhihuige)",
+ "To ZHG pip",
+ "ZHG FaceIndex",
+ "ZHG GetMaskArea",
+ "ZHG Image Levels",
+ "ZHG SaveImage",
+ "ZHG SmoothEdge",
+ "ZHG UltimateSDUpscale"
+ ],
+ {
+ "title_aux": "zhihuige-nodes-comfyui"
+ }
+ ],
+ "https://github.com/rcsaquino/comfyui-custom-nodes": [
+ [
+ "BackgroundRemover | rcsaquino",
+ "VAELoader | rcsaquino",
+ "VAEProcessor | rcsaquino"
+ ],
+ {
+ "title_aux": "rcsaquino/comfyui-custom-nodes"
+ }
+ ],
+ "https://github.com/receyuki/comfyui-prompt-reader-node": [
+ [
+ "SDBatchLoader",
+ "SDLoraLoader",
+ "SDLoraSelector",
+ "SDParameterExtractor",
+ "SDParameterGenerator",
+ "SDPromptMerger",
+ "SDPromptReader",
+ "SDPromptSaver",
+ "SDTypeConverter"
+ ],
+ {
+ "author": "receyuki",
+ "description": "ComfyUI node version of the SD Prompt Reader",
+ "nickname": "SD Prompt Reader",
+ "title": "SD Prompt Reader",
+ "title_aux": "comfyui-prompt-reader-node"
+ }
+ ],
+ "https://github.com/redhottensors/ComfyUI-Prediction": [
+ [
+ "SamplerCustomPrediction"
+ ],
+ {
+ "title_aux": "ComfyUI-Prediction"
+ }
+ ],
+ "https://github.com/rgthree/rgthree-comfy": [
+ [],
+ {
+ "author": "rgthree",
+ "description": "A bunch of nodes I created that I also find useful.",
+ "nickname": "rgthree",
+ "nodename_pattern": " \\(rgthree\\)$",
+ "title": "Comfy Nodes",
+ "title_aux": "rgthree's ComfyUI Nodes"
+ }
+ ],
+ "https://github.com/richinsley/Comfy-LFO": [
+ [
+ "LFO_Pulse",
+ "LFO_Sawtooth",
+ "LFO_Sine",
+ "LFO_Square",
+ "LFO_Triangle"
+ ],
+ {
+ "title_aux": "Comfy-LFO"
+ }
+ ],
+ "https://github.com/ricklove/comfyui-ricklove": [
+ [
+ "RL_Crop_Resize",
+ "RL_Crop_Resize_Batch",
+ "RL_Depth16",
+ "RL_Finetune_Analyze",
+ "RL_Finetune_Analyze_Batch",
+ "RL_Finetune_Variable",
+ "RL_Image_Shadow",
+ "RL_Image_Threshold_Channels",
+ "RL_Internet_Search",
+ "RL_LoadImageSequence",
+ "RL_Optical_Flow_Dip",
+ "RL_SaveImageSequence",
+ "RL_Uncrop",
+ "RL_Warp_Image",
+ "RL_Zoe_Depth_Map_Preprocessor",
+ "RL_Zoe_Depth_Map_Preprocessor_Raw_Infer",
+ "RL_Zoe_Depth_Map_Preprocessor_Raw_Process"
+ ],
+ {
+ "title_aux": "comfyui-ricklove"
+ }
+ ],
+ "https://github.com/rklaffehn/rk-comfy-nodes": [
+ [
+ "RK_CivitAIAddHashes",
+ "RK_CivitAIMetaChecker"
+ ],
+ {
+ "title_aux": "rk-comfy-nodes"
+ }
+ ],
+ "https://github.com/romeobuilderotti/ComfyUI-PNG-Metadata": [
+ [
+ "SetMetadataAll",
+ "SetMetadataString"
+ ],
+ {
+ "title_aux": "ComfyUI PNG Metadata"
+ }
+ ],
+ "https://github.com/rui40000/RUI-Nodes": [
+ [
+ "ABCondition",
+ "CharacterCount"
+ ],
+ {
+ "title_aux": "RUI-Nodes"
+ }
+ ],
+ "https://github.com/s1dlx/comfy_meh/raw/main/meh.py": [
+ [
+ "MergingExecutionHelper"
+ ],
+ {
+ "title_aux": "comfy_meh"
+ }
+ ],
+ "https://github.com/seanlynch/comfyui-optical-flow": [
+ [
+ "Apply optical flow",
+ "Compute optical flow",
+ "Visualize optical flow"
+ ],
+ {
+ "title_aux": "ComfyUI Optical Flow"
+ }
+ ],
+ "https://github.com/seanlynch/srl-nodes": [
+ [
+ "SRL Conditional Interrrupt",
+ "SRL Eval",
+ "SRL Filter Image List",
+ "SRL Format String"
+ ],
+ {
+ "title_aux": "SRL's nodes"
+ }
+ ],
+ "https://github.com/sergekatzmann/ComfyUI_Nimbus-Pack": [
+ [
+ "ImageResizeAndCropNode",
+ "ImageSquareAdapterNode"
+ ],
+ {
+ "title_aux": "ComfyUI_Nimbus-Pack"
+ }
+ ],
+ "https://github.com/shadowcz007/comfyui-consistency-decoder": [
+ [
+ "VAEDecodeConsistencyDecoder",
+ "VAELoaderConsistencyDecoder"
+ ],
+ {
+ "title_aux": "Consistency Decoder"
+ }
+ ],
+ "https://github.com/shadowcz007/comfyui-mixlab-nodes": [
+ [
+ "3DImage",
+ "AppInfo",
+ "AreaToMask",
+ "CenterImage",
+ "CharacterInText",
+ "ChatGPTOpenAI",
+ "CkptNames_",
+ "Color",
+ "DynamicDelayProcessor",
+ "EmbeddingPrompt",
+ "EnhanceImage",
+ "FaceToMask",
+ "FeatheredMask",
+ "FloatSlider",
+ "FloatingVideo",
+ "Font",
+ "GamePal",
+ "GetImageSize_",
+ "GradientImage",
+ "GridOutput",
+ "ImageColorTransfer",
+ "ImageCropByAlpha",
+ "IntNumber",
+ "JoinWithDelimiter",
+ "LaMaInpainting",
+ "LimitNumber",
+ "LoadImagesFromPath",
+ "LoadImagesFromURL",
+ "LoraNames_",
+ "MergeLayers",
+ "MirroredImage",
+ "MultiplicationNode",
+ "NewLayer",
+ "NoiseImage",
+ "OutlineMask",
+ "PromptImage",
+ "PromptSimplification",
+ "PromptSlide",
+ "RandomPrompt",
+ "ResizeImageMixlab",
+ "SamplerNames_",
+ "SaveImageToLocal",
+ "ScreenShare",
+ "Seed_",
+ "ShowLayer",
+ "ShowTextForGPT",
+ "SmoothMask",
+ "SpeechRecognition",
+ "SpeechSynthesis",
+ "SplitImage",
+ "SplitLongMask",
+ "SvgImage",
+ "SwitchByIndex",
+ "TESTNODE_",
+ "TESTNODE_TOKEN",
+ "TextImage",
+ "TextInput_",
+ "TextToNumber",
+ "TransparentImage",
+ "VAEDecodeConsistencyDecoder",
+ "VAELoaderConsistencyDecoder"
+ ],
+ {
+ "title_aux": "comfyui-mixlab-nodes"
+ }
+ ],
+ "https://github.com/shadowcz007/comfyui-ultralytics-yolo": [
+ [
+ "DetectByLabel"
+ ],
+ {
+ "title_aux": "comfyui-ultralytics-yolo"
+ }
+ ],
+ "https://github.com/shiimizu/ComfyUI-PhotoMaker-Plus": [
+ [
+ "PhotoMakerEncodePlus",
+ "PhotoMakerStyles",
+ "PrepImagesForClipVisionFromPath"
+ ],
+ {
+ "title_aux": "ComfyUI PhotoMaker Plus"
+ }
+ ],
+ "https://github.com/shiimizu/ComfyUI-TiledDiffusion": [
+ [
+ "NoiseInversion",
+ "TiledDiffusion",
+ "VAEDecodeTiled_TiledDiffusion",
+ "VAEEncodeTiled_TiledDiffusion"
+ ],
+ {
+ "title_aux": "Tiled Diffusion & VAE for ComfyUI"
+ }
+ ],
+ "https://github.com/shiimizu/ComfyUI_smZNodes": [
+ [
+ "smZ CLIPTextEncode",
+ "smZ Settings"
+ ],
+ {
+ "title_aux": "smZNodes"
+ }
+ ],
+ "https://github.com/shingo1228/ComfyUI-SDXL-EmptyLatentImage": [
+ [
+ "SDXL Empty Latent Image"
+ ],
+ {
+ "title_aux": "ComfyUI-SDXL-EmptyLatentImage"
+ }
+ ],
+ "https://github.com/shingo1228/ComfyUI-send-eagle-slim": [
+ [
+ "Send Eagle with text",
+ "Send Webp Image to Eagle"
+ ],
+ {
+ "title_aux": "ComfyUI-send-Eagle(slim)"
+ }
+ ],
+ "https://github.com/shockz0rz/ComfyUI_InterpolateEverything": [
+ [
+ "OpenposePreprocessorInterpolate"
+ ],
+ {
+ "title_aux": "InterpolateEverything"
+ }
+ ],
+ "https://github.com/shockz0rz/comfy-easy-grids": [
+ [
+ "FloatToText",
+ "GridFloatList",
+ "GridFloats",
+ "GridIntList",
+ "GridInts",
+ "GridLoras",
+ "GridStringList",
+ "GridStrings",
+ "ImageGridCommander",
+ "IntToText",
+ "SaveImageGrid",
+ "TextConcatenator"
+ ],
+ {
+ "title_aux": "comfy-easy-grids"
+ }
+ ],
+ "https://github.com/siliconflow/onediff_comfy_nodes": [
+ [
+ "CompareModel",
+ "ControlNetGraphLoader",
+ "ControlNetGraphSaver",
+ "ControlNetSpeedup",
+ "ModelGraphLoader",
+ "ModelGraphSaver",
+ "ModelSpeedup",
+ "ModuleDeepCacheSpeedup",
+ "OneDiffCheckpointLoaderSimple",
+ "SVDSpeedup",
+ "ShowImageDiff",
+ "VaeGraphLoader",
+ "VaeGraphSaver",
+ "VaeSpeedup"
+ ],
+ {
+ "title_aux": "OneDiff Nodes"
+ }
+ ],
+ "https://github.com/sipherxyz/comfyui-art-venture": [
+ [
+ "AV_CheckpointMerge",
+ "AV_CheckpointModelsToParametersPipe",
+ "AV_CheckpointSave",
+ "AV_ControlNetEfficientLoader",
+ "AV_ControlNetEfficientLoaderAdvanced",
+ "AV_ControlNetEfficientStacker",
+ "AV_ControlNetEfficientStackerSimple",
+ "AV_ControlNetLoader",
+ "AV_ControlNetPreprocessor",
+ "AV_LoraListLoader",
+ "AV_LoraListStacker",
+ "AV_LoraLoader",
+ "AV_ParametersPipeToCheckpointModels",
+ "AV_ParametersPipeToPrompts",
+ "AV_PromptsToParametersPipe",
+ "AV_SAMLoader",
+ "AV_VAELoader",
+ "AspectRatioSelector",
+ "BLIPCaption",
+ "BLIPLoader",
+ "BooleanPrimitive",
+ "ColorBlend",
+ "ColorCorrect",
+ "DeepDanbooruCaption",
+ "DependenciesEdit",
+ "Fooocus_KSampler",
+ "Fooocus_KSamplerAdvanced",
+ "GetBoolFromJson",
+ "GetFloatFromJson",
+ "GetIntFromJson",
+ "GetObjectFromJson",
+ "GetSAMEmbedding",
+ "GetTextFromJson",
+ "ISNetLoader",
+ "ISNetSegment",
+ "ImageAlphaComposite",
+ "ImageApplyChannel",
+ "ImageExtractChannel",
+ "ImageGaussianBlur",
+ "ImageMuxer",
+ "ImageRepeat",
+ "ImageScaleDown",
+ "ImageScaleDownBy",
+ "ImageScaleDownToSize",
+ "ImageScaleToMegapixels",
+ "LaMaInpaint",
+ "LoadImageAsMaskFromUrl",
+ "LoadImageFromUrl",
+ "LoadJsonFromUrl",
+ "MergeModels",
+ "NumberScaler",
+ "OverlayInpaintedImage",
+ "OverlayInpaintedLatent",
+ "PrepareImageAndMaskForInpaint",
+ "QRCodeGenerator",
+ "RandomFloat",
+ "RandomInt",
+ "SAMEmbeddingToImage",
+ "SDXLAspectRatioSelector",
+ "SDXLPromptStyler",
+ "SeedSelector",
+ "StringToInt",
+ "StringToNumber"
+ ],
+ {
+ "title_aux": "comfyui-art-venture"
+ }
+ ],
+ "https://github.com/skfoo/ComfyUI-Coziness": [
+ [
+ "LoraTextExtractor-b1f83aa2",
+ "MultiLoraLoader-70bf3d77"
+ ],
+ {
+ "title_aux": "ComfyUI-Coziness"
+ }
+ ],
+ "https://github.com/smagnetize/kb-comfyui-nodes": [
+ [
+ "SingleImageDataUrlLoader"
+ ],
+ {
+ "title_aux": "kb-comfyui-nodes"
+ }
+ ],
+ "https://github.com/space-nuko/ComfyUI-Disco-Diffusion": [
+ [
+ "DiscoDiffusion_DiscoDiffusion",
+ "DiscoDiffusion_DiscoDiffusionExtraSettings",
+ "DiscoDiffusion_GuidedDiffusionLoader",
+ "DiscoDiffusion_OpenAICLIPLoader"
+ ],
+ {
+ "title_aux": "Disco Diffusion"
+ }
+ ],
+ "https://github.com/space-nuko/ComfyUI-OpenPose-Editor": [
+ [
+ "Nui.OpenPoseEditor"
+ ],
+ {
+ "title_aux": "OpenPose Editor"
+ }
+ ],
+ "https://github.com/space-nuko/nui-suite": [
+ [
+ "Nui.DynamicPromptsTextGen",
+ "Nui.FeelingLuckyTextGen",
+ "Nui.OutputString"
+ ],
+ {
+ "title_aux": "nui suite"
+ }
+ ],
+ "https://github.com/spacepxl/ComfyUI-HQ-Image-Save": [
+ [
+ "LoadEXR",
+ "LoadLatentEXR",
+ "SaveEXR",
+ "SaveLatentEXR",
+ "SaveTiff"
+ ],
+ {
+ "title_aux": "ComfyUI-HQ-Image-Save"
+ }
+ ],
+ "https://github.com/spacepxl/ComfyUI-Image-Filters": [
+ [
+ "AdainImage",
+ "AdainLatent",
+ "AlphaClean",
+ "AlphaMatte",
+ "BatchAverageImage",
+ "BatchAverageUnJittered",
+ "BatchNormalizeImage",
+ "BatchNormalizeLatent",
+ "BlurImageFast",
+ "BlurMaskFast",
+ "ClampOutliers",
+ "ConvertNormals",
+ "DifferenceChecker",
+ "DilateErodeMask",
+ "EnhanceDetail",
+ "ExposureAdjust",
+ "GuidedFilterAlpha",
+ "ImageConstant",
+ "ImageConstantHSV",
+ "JitterImage",
+ "Keyer",
+ "LatentStats",
+ "NormalMapSimple",
+ "OffsetLatentImage",
+ "RemapRange",
+ "Tonemap",
+ "UnJitterImage",
+ "UnTonemap"
+ ],
+ {
+ "title_aux": "ComfyUI-Image-Filters"
+ }
+ ],
+ "https://github.com/spacepxl/ComfyUI-RAVE": [
+ [
+ "ConditioningDebug",
+ "ImageGridCompose",
+ "ImageGridDecompose",
+ "KSamplerRAVE",
+ "LatentGridCompose",
+ "LatentGridDecompose"
+ ],
+ {
+ "title_aux": "ComfyUI-RAVE"
+ }
+ ],
+ "https://github.com/spinagon/ComfyUI-seam-carving": [
+ [
+ "SeamCarving"
+ ],
+ {
+ "title_aux": "ComfyUI-seam-carving"
+ }
+ ],
+ "https://github.com/spinagon/ComfyUI-seamless-tiling": [
+ [
+ "CircularVAEDecode",
+ "MakeCircularVAE",
+ "OffsetImage",
+ "SeamlessTile"
+ ],
+ {
+ "title_aux": "Seamless tiling Node for ComfyUI"
+ }
+ ],
+ "https://github.com/spro/comfyui-mirror": [
+ [
+ "LatentMirror"
+ ],
+ {
+ "title_aux": "Latent Mirror node for ComfyUI"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_UltimateSDUpscale": [
+ [
+ "UltimateSDUpscale",
+ "UltimateSDUpscaleNoUpscale"
+ ],
+ {
+ "title_aux": "UltimateSDUpscale"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_fabric": [
+ [
+ "FABRICPatchModel",
+ "FABRICPatchModelAdv",
+ "KSamplerAdvFABRICAdv",
+ "KSamplerFABRIC",
+ "KSamplerFABRICAdv"
+ ],
+ {
+ "title_aux": "ComfyUI fabric"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_restart_sampling": [
+ [
+ "KRestartSampler",
+ "KRestartSamplerAdv",
+ "KRestartSamplerSimple"
+ ],
+ {
+ "title_aux": "Restart Sampling"
+ }
+ ],
+ "https://github.com/ssitu/ComfyUI_roop": [
+ [
+ "RoopImproved",
+ "roop"
+ ],
+ {
+ "title_aux": "ComfyUI roop"
+ }
+ ],
+ "https://github.com/storyicon/comfyui_segment_anything": [
+ [
+ "GroundingDinoModelLoader (segment anything)",
+ "GroundingDinoSAMSegment (segment anything)",
+ "InvertMask (segment anything)",
+ "IsMaskEmpty",
+ "SAMModelLoader (segment anything)"
+ ],
+ {
+ "title_aux": "segment anything"
+ }
+ ],
+ "https://github.com/strimmlarn/ComfyUI_Strimmlarns_aesthetic_score": [
+ [
+ "AesthetlcScoreSorter",
+ "CalculateAestheticScore",
+ "LoadAesteticModel",
+ "ScoreToNumber"
+ ],
+ {
+ "title_aux": "ComfyUI_Strimmlarns_aesthetic_score"
+ }
+ ],
+ "https://github.com/styler00dollar/ComfyUI-deepcache": [
+ [
+ "DeepCache"
+ ],
+ {
+ "title_aux": "ComfyUI-deepcache"
+ }
+ ],
+ "https://github.com/styler00dollar/ComfyUI-sudo-latent-upscale": [
+ [
+ "SudoLatentUpscale"
+ ],
+ {
+ "title_aux": "ComfyUI-sudo-latent-upscale"
+ }
+ ],
+ "https://github.com/syllebra/bilbox-comfyui": [
+ [
+ "BilboXLut",
+ "BilboXPhotoPrompt",
+ "BilboXVignette"
+ ],
+ {
+ "title_aux": "BilboX's ComfyUI Custom Nodes"
+ }
+ ],
+ "https://github.com/sylym/comfy_vid2vid": [
+ [
+ "CheckpointLoaderSimpleSequence",
+ "DdimInversionSequence",
+ "KSamplerSequence",
+ "LoadImageMaskSequence",
+ "LoadImageSequence",
+ "LoraLoaderSequence",
+ "SetLatentNoiseSequence",
+ "TrainUnetSequence",
+ "VAEEncodeForInpaintSequence"
+ ],
+ {
+ "title_aux": "Vid2vid"
+ }
+ ],
+ "https://github.com/szhublox/ambw_comfyui": [
+ [
+ "Auto Merge Block Weighted",
+ "CLIPMergeSimple",
+ "CheckpointSave",
+ "ModelMergeBlocks",
+ "ModelMergeSimple"
+ ],
+ {
+ "title_aux": "Auto-MBW"
+ }
+ ],
+ "https://github.com/taabata/Comfy_Syrian_Falcon_Nodes/raw/main/SyrianFalconNodes.py": [
+ [
+ "CompositeImage",
+ "KSamplerAlternate",
+ "KSamplerPromptEdit",
+ "KSamplerPromptEditAndAlternate",
+ "LoopBack",
+ "QRGenerate",
+ "WordAsImage"
+ ],
+ {
+ "title_aux": "Syrian Falcon Nodes"
+ }
+ ],
+ "https://github.com/taabata/LCM_Inpaint-Outpaint_Comfy": [
+ [
+ "ComfyNodesToSaveCanvas",
+ "FloatNumber",
+ "FreeU_LCM",
+ "ImageOutputToComfyNodes",
+ "ImageShuffle",
+ "ImageSwitch",
+ "LCMGenerate",
+ "LCMGenerate_ReferenceOnly",
+ "LCMGenerate_SDTurbo",
+ "LCMGenerate_img2img",
+ "LCMGenerate_img2img_IPAdapter",
+ "LCMGenerate_img2img_controlnet",
+ "LCMGenerate_inpaintv2",
+ "LCMGenerate_inpaintv3",
+ "LCMLoader",
+ "LCMLoader_RefInpaint",
+ "LCMLoader_ReferenceOnly",
+ "LCMLoader_SDTurbo",
+ "LCMLoader_controlnet",
+ "LCMLoader_controlnet_inpaint",
+ "LCMLoader_img2img",
+ "LCMLoraLoader_inpaint",
+ "LCMLoraLoader_ipadapter",
+ "LCMLora_inpaint",
+ "LCMLora_ipadapter",
+ "LCMT2IAdapter",
+ "LCM_IPAdapter",
+ "LCM_IPAdapter_inpaint",
+ "LCM_outpaint_prep",
+ "LoadImageNode_LCM",
+ "Loader_SegmindVega",
+ "OutpaintCanvasTool",
+ "SaveImage_Canvas",
+ "SaveImage_LCM",
+ "SaveImage_Puzzle",
+ "SaveImage_PuzzleV2",
+ "SegmindVega",
+ "SettingsSwitch",
+ "stitch"
+ ],
+ {
+ "title_aux": "LCM_Inpaint-Outpaint_Comfy"
+ }
+ ],
+ "https://github.com/talesofai/comfyui-browser": [
+ [
+ "DifyTextGenerator //Browser",
+ "LoadImageByUrl //Browser",
+ "SelectInputs //Browser",
+ "UploadToRemote //Browser",
+ "XyzPlot //Browser"
+ ],
+ {
+ "title_aux": "ComfyUI Browser"
+ }
+ ],
+ "https://github.com/theUpsider/ComfyUI-Logic": [
+ [
+ "Bool",
+ "Compare",
+ "DebugPrint",
+ "Float",
+ "If ANY execute A else B",
+ "Int",
+ "String"
+ ],
+ {
+ "title_aux": "ComfyUI-Logic"
+ }
+ ],
+ "https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader": [
+ [
+ "Load Styles CSV"
+ ],
+ {
+ "title_aux": "Styles CSV Loader Extension for ComfyUI"
+ }
+ ],
+ "https://github.com/thecooltechguy/ComfyUI-MagicAnimate": [
+ [
+ "MagicAnimate",
+ "MagicAnimateModelLoader"
+ ],
+ {
+ "title_aux": "ComfyUI-MagicAnimate"
+ }
+ ],
+ "https://github.com/thecooltechguy/ComfyUI-Stable-Video-Diffusion": [
+ [
+ "SVDDecoder",
+ "SVDModelLoader",
+ "SVDSampler",
+ "SVDSimpleImg2Vid"
+ ],
+ {
+ "title_aux": "ComfyUI Stable Video Diffusion"
+ }
+ ],
+ "https://github.com/thedyze/save-image-extended-comfyui": [
+ [
+ "SaveImageExtended"
+ ],
+ {
+ "title_aux": "Save Image Extended for ComfyUI"
+ }
+ ],
+ "https://github.com/tocubed/ComfyUI-AudioReactor": [
+ [
+ "AudioFrameTransformBeats",
+ "AudioFrameTransformShadertoy",
+ "AudioLoadPath",
+ "Shadertoy"
+ ],
+ {
+ "title_aux": "ComfyUI-AudioReactor"
+ }
+ ],
+ "https://github.com/toyxyz/ComfyUI_toyxyz_test_nodes": [
+ [
+ "CaptureWebcam",
+ "LatentDelay",
+ "LoadWebcamImage",
+ "SaveImagetoPath"
+ ],
+ {
+ "title_aux": "ComfyUI_toyxyz_test_nodes"
+ }
+ ],
+ "https://github.com/trojblue/trNodes": [
+ [
+ "JpgConvertNode",
+ "trColorCorrection",
+ "trLayering",
+ "trRouter",
+ "trRouterLonger"
+ ],
+ {
+ "title_aux": "trNodes"
+ }
+ ],
+ "https://github.com/trumanwong/ComfyUI-NSFW-Detection": [
+ [
+ "NSFWDetection"
+ ],
+ {
+ "title_aux": "ComfyUI-NSFW-Detection"
+ }
+ ],
+ "https://github.com/ttulttul/ComfyUI-Iterative-Mixer": [
+ [
+ "Batch Unsampler",
+ "Iterative Mixing KSampler",
+ "Iterative Mixing KSampler Advanced",
+ "IterativeMixingSampler",
+ "IterativeMixingScheduler",
+ "IterativeMixingSchedulerAdvanced",
+ "Latent Batch Comparison Plot",
+ "Latent Batch Statistics Plot",
+ "MixingMaskGenerator"
+ ],
+ {
+ "title_aux": "ComfyUI Iterative Mixing Nodes"
+ }
+ ],
+ "https://github.com/ttulttul/ComfyUI-Tensor-Operations": [
+ [
+ "Image Match Normalize",
+ "Latent Match Normalize"
+ ],
+ {
+ "title_aux": "ComfyUI-Tensor-Operations"
+ }
+ ],
+ "https://github.com/tudal/Hakkun-ComfyUI-nodes/raw/main/hakkun_nodes.py": [
+ [
+ "Any Converter",
+ "Calculate Upscale",
+ "Image Resize To Height",
+ "Image Resize To Width",
+ "Image size to string",
+ "Load Random Image",
+ "Load Text",
+ "Multi Text Merge",
+ "Prompt Parser",
+ "Random Line",
+ "Random Line 4"
+ ],
+ {
+ "title_aux": "Hakkun-ComfyUI-nodes"
+ }
+ ],
+ "https://github.com/tusharbhutt/Endless-Nodes": [
+ [
+ "ESS Aesthetic Scoring",
+ "ESS Aesthetic Scoring Auto",
+ "ESS Combo Parameterizer",
+ "ESS Combo Parameterizer & Prompts",
+ "ESS Eight Input Random",
+ "ESS Eight Input Text Switch",
+ "ESS Float to Integer",
+ "ESS Float to Number",
+ "ESS Float to String",
+ "ESS Float to X",
+ "ESS Global Envoy",
+ "ESS Image Reward",
+ "ESS Image Reward Auto",
+ "ESS Image Saver with JSON",
+ "ESS Integer to Float",
+ "ESS Integer to Number",
+ "ESS Integer to String",
+ "ESS Integer to X",
+ "ESS Number to Float",
+ "ESS Number to Integer",
+ "ESS Number to String",
+ "ESS Number to X",
+ "ESS Parameterizer",
+ "ESS Parameterizer & Prompts",
+ "ESS Six Float Output",
+ "ESS Six Input Random",
+ "ESS Six Input Text Switch",
+ "ESS Six Integer IO Switch",
+ "ESS Six Integer IO Widget",
+ "ESS String to Float",
+ "ESS String to Integer",
+ "ESS String to Num",
+ "ESS String to X",
+ "\u267e\ufe0f\ud83c\udf0a\u2728 Image Saver with JSON"
+ ],
+ {
+ "author": "BiffMunky",
+ "description": "A small set of nodes I created for various numerical and text inputs. Features image saver with ability to have JSON saved to separate folder, parameter collection nodes, two aesthetic scoring models, switches for text and numbers, and conversion of string to numeric and vice versa.",
+ "nickname": "\u267e\ufe0f\ud83c\udf0a\u2728",
+ "title": "Endless \ufe0f\ud83c\udf0a\u2728 Nodes",
+ "title_aux": "Endless \ufe0f\ud83c\udf0a\u2728 Nodes"
+ }
+ ],
+ "https://github.com/twri/sdxl_prompt_styler": [
+ [
+ "SDXLPromptStyler",
+ "SDXLPromptStylerAdvanced"
+ ],
+ {
+ "title_aux": "SDXL Prompt Styler"
+ }
+ ],
+ "https://github.com/uarefans/ComfyUI-Fans": [
+ [
+ "Fans Prompt Styler Negative",
+ "Fans Prompt Styler Positive",
+ "Fans Styler",
+ "Fans Text Concatenate"
+ ],
+ {
+ "title_aux": "ComfyUI-Fans"
+ }
+ ],
+ "https://github.com/vanillacode314/SimpleWildcardsComfyUI": [
+ [
+ "SimpleConcat",
+ "SimpleWildcard"
+ ],
+ {
+ "author": "VanillaCode314",
+ "description": "A simple wildcard node for ComfyUI. Can also be used a style prompt node.",
+ "nickname": "Simple Wildcard",
+ "title": "Simple Wildcard",
+ "title_aux": "Simple Wildcard"
+ }
+ ],
+ "https://github.com/vienteck/ComfyUI-Chat-GPT-Integration": [
+ [
+ "ChatGptPrompt"
+ ],
+ {
+ "title_aux": "ComfyUI-Chat-GPT-Integration"
+ }
+ ],
+ "https://github.com/violet-chen/comfyui-psd2png": [
+ [
+ "Psd2Png"
+ ],
+ {
+ "title_aux": "comfyui-psd2png"
+ }
+ ],
+ "https://github.com/wallish77/wlsh_nodes": [
+ [
+ "Alternating KSampler (WLSH)",
+ "Build Filename String (WLSH)",
+ "CLIP +/- w/Text Unified (WLSH)",
+ "CLIP Positive-Negative (WLSH)",
+ "CLIP Positive-Negative XL (WLSH)",
+ "CLIP Positive-Negative XL w/Text (WLSH)",
+ "CLIP Positive-Negative w/Text (WLSH)",
+ "Checkpoint Loader w/Name (WLSH)",
+ "Empty Latent by Pixels (WLSH)",
+ "Empty Latent by Ratio (WLSH)",
+ "Empty Latent by Size (WLSH)",
+ "Generate Border Mask (WLSH)",
+ "Grayscale Image (WLSH)",
+ "Image Load with Metadata (WLSH)",
+ "Image Save with Prompt (WLSH)",
+ "Image Save with Prompt File (WLSH)",
+ "Image Save with Prompt/Info (WLSH)",
+ "Image Save with Prompt/Info File (WLSH)",
+ "Image Scale By Factor (WLSH)",
+ "Image Scale by Shortside (WLSH)",
+ "KSamplerAdvanced (WLSH)",
+ "Multiply Integer (WLSH)",
+ "Outpaint to Image (WLSH)",
+ "Prompt Weight (WLSH)",
+ "Quick Resolution Multiply (WLSH)",
+ "Resolutions by Ratio (WLSH)",
+ "SDXL Quick Empty Latent (WLSH)",
+ "SDXL Quick Image Scale (WLSH)",
+ "SDXL Resolutions (WLSH)",
+ "SDXL Steps (WLSH)",
+ "Save Positive Prompt(WLSH)",
+ "Save Prompt (WLSH)",
+ "Save Prompt/Info (WLSH)",
+ "Seed and Int (WLSH)",
+ "Seed to Number (WLSH)",
+ "Simple Pattern Replace (WLSH)",
+ "Simple String Combine (WLSH)",
+ "Time String (WLSH)",
+ "Upscale by Factor with Model (WLSH)",
+ "VAE Encode for Inpaint w/Padding (WLSH)"
+ ],
+ {
+ "title_aux": "wlsh_nodes"
+ }
+ ],
+ "https://github.com/whatbirdisthat/cyberdolphin": [
+ [
+ "\ud83d\udc2c Gradio ChatInterface",
+ "\ud83d\udc2c OpenAI Advanced",
+ "\ud83d\udc2c OpenAI Compatible",
+ "\ud83d\udc2c OpenAI DALL\u00b7E",
+ "\ud83d\udc2c OpenAI Simple"
+ ],
+ {
+ "title_aux": "cyberdolphin"
+ }
+ ],
+ "https://github.com/whmc76/ComfyUI-Openpose-Editor-Plus": [
+ [
+ "CDL.OpenPoseEditorPlus"
+ ],
+ {
+ "title_aux": "ComfyUI-Openpose-Editor-Plus"
+ }
+ ],
+ "https://github.com/wmatson/easy-comfy-nodes": [
+ [
+ "EZAssocDictNode",
+ "EZAssocImgNode",
+ "EZAssocStrNode",
+ "EZEmptyDictNode",
+ "EZHttpPostNode",
+ "EZLoadImgBatchFromUrlsNode",
+ "EZLoadImgFromUrlNode",
+ "EZRemoveImgBackground",
+ "EZS3Uploader",
+ "EZVideoCombiner"
+ ],
+ {
+ "title_aux": "easy-comfy-nodes"
+ }
+ ],
+ "https://github.com/wolfden/ComfyUi_PromptStylers": [
+ [
+ "SDXLPromptStylerAll",
+ "SDXLPromptStylerHorror",
+ "SDXLPromptStylerMisc",
+ "SDXLPromptStylerbyArtist",
+ "SDXLPromptStylerbyCamera",
+ "SDXLPromptStylerbyComposition",
+ "SDXLPromptStylerbyCyberpunkSurrealism",
+ "SDXLPromptStylerbyDepth",
+ "SDXLPromptStylerbyEnvironment",
+ "SDXLPromptStylerbyFantasySetting",
+ "SDXLPromptStylerbyFilter",
+ "SDXLPromptStylerbyFocus",
+ "SDXLPromptStylerbyImpressionism",
+ "SDXLPromptStylerbyLighting",
+ "SDXLPromptStylerbyMileHigh",
+ "SDXLPromptStylerbyMood",
+ "SDXLPromptStylerbyMythicalCreature",
+ "SDXLPromptStylerbyOriginal",
+ "SDXLPromptStylerbyQuantumRealism",
+ "SDXLPromptStylerbySteamPunkRealism",
+ "SDXLPromptStylerbySubject",
+ "SDXLPromptStylerbySurrealism",
+ "SDXLPromptStylerbyTheme",
+ "SDXLPromptStylerbyTimeofDay",
+ "SDXLPromptStylerbyWyvern",
+ "SDXLPromptbyCelticArt",
+ "SDXLPromptbyContemporaryNordicArt",
+ "SDXLPromptbyFashionArt",
+ "SDXLPromptbyGothicRevival",
+ "SDXLPromptbyIrishFolkArt",
+ "SDXLPromptbyRomanticNationalismArt",
+ "SDXLPromptbySportsArt",
+ "SDXLPromptbyStreetArt",
+ "SDXLPromptbyVikingArt",
+ "SDXLPromptbyWildlifeArt"
+ ],
+ {
+ "title_aux": "SDXL Prompt Styler (customized version by wolfden)"
+ }
+ ],
+ "https://github.com/wolfden/ComfyUi_String_Function_Tree": [
+ [
+ "StringFunction"
+ ],
+ {
+ "title_aux": "ComfyUi_String_Function_Tree"
+ }
+ ],
+ "https://github.com/wsippel/comfyui_ws/raw/main/sdxl_utility.py": [
+ [
+ "SDXLResolutionPresets"
+ ],
+ {
+ "title_aux": "SDXLResolutionPresets"
+ }
+ ],
+ "https://github.com/wutipong/ComfyUI-TextUtils": [
+ [
+ "Text Utils - Join N-Elements of String List",
+ "Text Utils - Join String List",
+ "Text Utils - Join Strings",
+ "Text Utils - Split String to List"
+ ],
+ {
+ "title_aux": "ComfyUI-TextUtils"
+ }
+ ],
+ "https://github.com/wwwins/ComfyUI-Simple-Aspect-Ratio": [
+ [
+ "SimpleAspectRatio"
+ ],
+ {
+ "title_aux": "ComfyUI-Simple-Aspect-Ratio"
+ }
+ ],
+ "https://github.com/xXAdonesXx/NodeGPT": [
+ [
+ "AppendAgent",
+ "Assistant",
+ "Chat",
+ "ChatGPT",
+ "CombineInput",
+ "Conditioning",
+ "CostumeAgent_1",
+ "CostumeAgent_2",
+ "CostumeMaster_1",
+ "Critic",
+ "DisplayString",
+ "DisplayTextAsImage",
+ "EVAL",
+ "Engineer",
+ "Executor",
+ "GroupChat",
+ "Image_generation_Conditioning",
+ "LM_Studio",
+ "LoadAPIconfig",
+ "LoadTXT",
+ "MemGPT",
+ "Memory_Excel",
+ "Model_1",
+ "Ollama",
+ "Output2String",
+ "Planner",
+ "Scientist",
+ "TextCombine",
+ "TextGeneration",
+ "TextGenerator",
+ "TextInput",
+ "TextOutput",
+ "UserProxy",
+ "llama-cpp",
+ "llava",
+ "oobaboogaOpenAI"
+ ],
+ {
+ "title_aux": "NodeGPT"
+ }
+ ],
+ "https://github.com/xiaoxiaodesha/hd_node": [
+ [
+ "Combine HDMasks",
+ "Cover HDMasks",
+ "HD FaceIndex",
+ "HD GetMaskArea",
+ "HD Image Levels",
+ "HD SmoothEdge",
+ "HD UltimateSDUpscale"
+ ],
+ {
+ "title_aux": "hd-nodes-comfyui"
+ }
+ ],
+ "https://github.com/yffyhk/comfyui_auto_danbooru": [
+ [
+ "GetDanbooru",
+ "TagEncode"
+ ],
+ {
+ "title_aux": "comfyui_auto_danbooru"
+ }
+ ],
+ "https://github.com/yolain/ComfyUI-Easy-Use": [
+ [
+ "dynamicThresholdingFull",
+ "easy LLLiteLoader",
+ "easy XYInputs: CFG Scale",
+ "easy XYInputs: Checkpoint",
+ "easy XYInputs: ControlNet",
+ "easy XYInputs: Denoise",
+ "easy XYInputs: Lora",
+ "easy XYInputs: ModelMergeBlocks",
+ "easy XYInputs: NegativeCond",
+ "easy XYInputs: NegativeCondList",
+ "easy XYInputs: PositiveCond",
+ "easy XYInputs: PositiveCondList",
+ "easy XYInputs: PromptSR",
+ "easy XYInputs: Sampler/Scheduler",
+ "easy XYInputs: Seeds++ Batch",
+ "easy XYInputs: Steps",
+ "easy XYPlot",
+ "easy XYPlotAdvanced",
+ "easy a1111Loader",
+ "easy boolean",
+ "easy cleanGpuUsed",
+ "easy comfyLoader",
+ "easy compare",
+ "easy controlnetLoader",
+ "easy controlnetLoaderADV",
+ "easy convertAnything",
+ "easy detailerFix",
+ "easy float",
+ "easy fooocusInpaintLoader",
+ "easy fullLoader",
+ "easy fullkSampler",
+ "easy globalSeed",
+ "easy hiresFix",
+ "easy if",
+ "easy imageInsetCrop",
+ "easy imagePixelPerfect",
+ "easy imageRemoveBG",
+ "easy imageSave",
+ "easy imageScaleDown",
+ "easy imageScaleDownBy",
+ "easy imageScaleDownToSize",
+ "easy imageSize",
+ "easy imageSizeByLongerSide",
+ "easy imageSizeBySide",
+ "easy imageSwitch",
+ "easy imageToMask",
+ "easy int",
+ "easy isSDXL",
+ "easy joinImageBatch",
+ "easy kSampler",
+ "easy kSamplerDownscaleUnet",
+ "easy kSamplerInpainting",
+ "easy kSamplerSDTurbo",
+ "easy kSamplerTiled",
+ "easy latentCompositeMaskedWithCond",
+ "easy latentNoisy",
+ "easy loraStack",
+ "easy negative",
+ "easy pipeIn",
+ "easy pipeOut",
+ "easy pipeToBasicPipe",
+ "easy portraitMaster",
+ "easy poseEditor",
+ "easy positive",
+ "easy preDetailerFix",
+ "easy preSampling",
+ "easy preSamplingAdvanced",
+ "easy preSamplingDynamicCFG",
+ "easy preSamplingSdTurbo",
+ "easy promptList",
+ "easy rangeFloat",
+ "easy rangeInt",
+ "easy samLoaderPipe",
+ "easy seed",
+ "easy showAnything",
+ "easy showLoaderSettingsNames",
+ "easy showSpentTime",
+ "easy string",
+ "easy stylesSelector",
+ "easy svdLoader",
+ "easy ultralyticsDetectorPipe",
+ "easy unSampler",
+ "easy wildcards",
+ "easy xyAny",
+ "easy zero123Loader"
+ ],
+ {
+ "title_aux": "ComfyUI Easy Use"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyImageSubmit": [
+ [
+ "DTSimpleSubmitImage",
+ "DTSubmitImage"
+ ],
+ {
+ "title_aux": "Comfy AI DoubTech.ai Image Sumission Node"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyLoaders": [
+ [
+ "DTCLIPLoader",
+ "DTCLIPVisionLoader",
+ "DTCheckpointLoader",
+ "DTCheckpointLoaderSimple",
+ "DTControlNetLoader",
+ "DTDiffControlNetLoader",
+ "DTDiffusersLoader",
+ "DTGLIGENLoader",
+ "DTLoadImage",
+ "DTLoadImageMask",
+ "DTLoadLatent",
+ "DTLoraLoader",
+ "DTLorasLoader",
+ "DTStyleModelLoader",
+ "DTUpscaleModelLoader",
+ "DTVAELoader",
+ "DTunCLIPCheckpointLoader"
+ ],
+ {
+ "title_aux": "Comfy UI Online Loaders"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyPromptAgent": [
+ [
+ "DTPromptAgent",
+ "DTPromptAgentString"
+ ],
+ {
+ "title_aux": "Comfy UI Prompt Agent"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyQRCodes": [
+ [
+ "QRCode"
+ ],
+ {
+ "title_aux": "Comfy UI QR Codes"
+ }
+ ],
+ "https://github.com/yolanother/DTAIComfyVariables": [
+ [
+ "DTCLIPTextEncode",
+ "DTSingleLineStringVariable",
+ "DTSingleLineStringVariableNoClip",
+ "FloatVariable",
+ "IntVariable",
+ "StringFormat",
+ "StringFormatSingleLine",
+ "StringVariable"
+ ],
+ {
+ "title_aux": "Variables for Comfy UI"
+ }
+ ],
+ "https://github.com/yolanother/DTAIImageToTextNode": [
+ [
+ "DTAIImageToTextNode",
+ "DTAIImageUrlToTextNode"
+ ],
+ {
+ "title_aux": "Image to Text Node"
+ }
+ ],
+ "https://github.com/youyegit/tdxh_node_comfyui": [
+ [
+ "TdxhBoolNumber",
+ "TdxhClipVison",
+ "TdxhControlNetApply",
+ "TdxhControlNetProcessor",
+ "TdxhFloatInput",
+ "TdxhImageToSize",
+ "TdxhImageToSizeAdvanced",
+ "TdxhImg2ImgLatent",
+ "TdxhIntInput",
+ "TdxhLoraLoader",
+ "TdxhOnOrOff",
+ "TdxhReference",
+ "TdxhStringInput",
+ "TdxhStringInputTranslator"
+ ],
+ {
+ "title_aux": "tdxh_node_comfyui"
+ }
+ ],
+ "https://github.com/yuvraj108c/ComfyUI-Pronodes": [
+ [
+ "LoadYoutubeVideoNode"
+ ],
+ {
+ "title_aux": "ComfyUI-Pronodes"
+ }
+ ],
+ "https://github.com/yuvraj108c/ComfyUI-Whisper": [
+ [
+ "Add Subtitles To Background",
+ "Add Subtitles To Frames",
+ "Apply Whisper",
+ "Resize Cropped Subtitles"
+ ],
+ {
+ "title_aux": "ComfyUI Whisper"
+ }
+ ],
+ "https://github.com/zcfrank1st/Comfyui-Toolbox": [
+ [
+ "PreviewJson",
+ "PreviewVideo",
+ "SaveJson",
+ "TestJsonPreview"
+ ],
+ {
+ "title_aux": "Comfyui-Toolbox"
+ }
+ ],
+ "https://github.com/zcfrank1st/Comfyui-Yolov8": [
+ [
+ "Yolov8Detection",
+ "Yolov8Segmentation"
+ ],
+ {
+ "title_aux": "ComfyUI Yolov8"
+ }
+ ],
+ "https://github.com/zcfrank1st/comfyui_visual_anagrams": [
+ [
+ "VisualAnagramsAnimate",
+ "VisualAnagramsSample"
+ ],
+ {
+ "title_aux": "comfyui_visual_anagram"
+ }
+ ],
+ "https://github.com/zer0TF/cute-comfy": [
+ [
+ "Cute.Placeholder"
+ ],
+ {
+ "title_aux": "Cute Comfy"
+ }
+ ],
+ "https://github.com/zfkun/ComfyUI_zfkun": [
+ [
+ "ZFLoadImagePath",
+ "ZFPreviewText",
+ "ZFPreviewTextMultiline",
+ "ZFShareScreen",
+ "ZFTextTranslation"
+ ],
+ {
+ "title_aux": "ComfyUI_zfkun"
+ }
+ ],
+ "https://github.com/zhongpei/ComfyUI-InstructIR": [
+ [
+ "InstructIRProcess",
+ "LoadInstructIRModel"
+ ],
+ {
+ "title_aux": "ComfyUI for InstructIR"
+ }
+ ],
+ "https://github.com/zhongpei/Comfyui_image2prompt": [
+ [
+ "Image2Text",
+ "LoadImage2TextModel"
+ ],
+ {
+ "title_aux": "Comfyui_image2prompt"
+ }
+ ],
+ "https://github.com/zhuanqianfish/ComfyUI-EasyNode": [
+ [
+ "EasyCaptureNode",
+ "EasyVideoOutputNode",
+ "SendImageWebSocket"
+ ],
+ {
+ "title_aux": "EasyCaptureNode for ComfyUI"
+ }
+ ],
+ "https://raw.githubusercontent.com/throttlekitty/SDXLCustomAspectRatio/main/SDXLAspectRatio.py": [
+ [
+ "SDXLAspectRatio"
+ ],
+ {
+ "title_aux": "SDXLCustomAspectRatio"
+ }
+ ]
+ }
+ """;
+}
diff --git a/StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs b/StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs
index c8614ada..da41388a 100644
--- a/StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs
+++ b/StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs
@@ -7,4 +7,7 @@ public class DownloadPackageVersionOptions
public string? VersionTag { get; set; }
public bool IsLatest { get; set; }
public bool IsPrerelease { get; set; }
+
+ public string GetReadableVersionString() =>
+ !string.IsNullOrWhiteSpace(VersionTag) ? VersionTag : $"{BranchName}@{CommitHash?[..7]}";
}
diff --git a/StabilityMatrix.Core/Models/HybridModelFile.cs b/StabilityMatrix.Core/Models/HybridModelFile.cs
index 5e0d7275..8e21cb39 100644
--- a/StabilityMatrix.Core/Models/HybridModelFile.cs
+++ b/StabilityMatrix.Core/Models/HybridModelFile.cs
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
+using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Database;
namespace StabilityMatrix.Core.Models;
@@ -20,7 +21,7 @@ public record HybridModelFile
///
public static HybridModelFile None { get; } = FromRemote("@none");
- private string? RemoteName { get; init; }
+ public string? RemoteName { get; init; }
public LocalModelFile? Local { get; init; }
@@ -67,6 +68,11 @@ public record HybridModelFile
return "Default";
}
+ if (ReferenceEquals(this, RemoteModels.ControlNetReferenceOnlyModel))
+ {
+ return "Reference Only";
+ }
+
var fileName = Path.GetFileNameWithoutExtension(RelativePath);
if (
diff --git a/StabilityMatrix.Core/Models/Inference/LayerDiffuseMode.cs b/StabilityMatrix.Core/Models/Inference/LayerDiffuseMode.cs
new file mode 100644
index 00000000..35621f60
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Inference/LayerDiffuseMode.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace StabilityMatrix.Core.Models.Inference;
+
+public enum LayerDiffuseMode
+{
+ ///
+ /// The layer diffuse mode is not set.
+ ///
+ [Display(Name = "None")]
+ None,
+
+ ///
+ /// Generate foreground only with transparency. SD1.5
+ ///
+ [Display(Name = "(SD 1.5) Generate Foreground with Transparency")]
+ GenerateForegroundWithTransparencySD15,
+
+ ///
+ /// Generate foreground only with transparency. SDXL
+ ///
+ [Display(Name = "(SDXL) Generate Foreground with Transparency")]
+ GenerateForegroundWithTransparencySDXL,
+}
diff --git a/StabilityMatrix.Core/Models/Inference/ModuleApplyStepTemporaryArgs.cs b/StabilityMatrix.Core/Models/Inference/ModuleApplyStepTemporaryArgs.cs
new file mode 100644
index 00000000..e76ea875
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Inference/ModuleApplyStepTemporaryArgs.cs
@@ -0,0 +1,46 @@
+using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+
+namespace StabilityMatrix.Core.Models.Inference;
+
+public class ModuleApplyStepTemporaryArgs
+{
+ ///
+ /// Temporary Primary apply step, used by ControlNet ReferenceOnly which changes the latent.
+ ///
+ public PrimaryNodeConnection? Primary { get; set; }
+
+ public VAENodeConnection? PrimaryVAE { get; set; }
+
+ ///
+ /// Used by Reference-Only ControlNet to indicate that has been batched.
+ ///
+ public bool IsPrimaryTempBatched { get; set; }
+
+ ///
+ /// When is true, this is the index of the temp batch to pick after sampling.
+ ///
+ public int PrimaryTempBatchPickIndex { get; set; }
+
+ public Dictionary Models { get; set; } =
+ new() { ["Base"] = new ModelConnections("Base"), ["Refiner"] = new ModelConnections("Refiner") };
+
+ public ModelConnections Base => Models["Base"];
+ public ModelConnections Refiner => Models["Refiner"];
+
+ public ConditioningConnections GetRefinerOrBaseConditioning()
+ {
+ return Refiner.Conditioning
+ ?? Base.Conditioning
+ ?? throw new NullReferenceException("No Refiner or Base Conditioning");
+ }
+
+ public ModelNodeConnection GetRefinerOrBaseModel()
+ {
+ return Refiner.Model ?? Base.Model ?? throw new NullReferenceException("No Refiner or Base Model");
+ }
+
+ public VAENodeConnection GetDefaultVAE()
+ {
+ return PrimaryVAE ?? Refiner.VAE ?? Base.VAE ?? throw new NullReferenceException("No VAE");
+ }
+}
diff --git a/StabilityMatrix.Core/Models/InferenceRunCustomPromptEventArgs.cs b/StabilityMatrix.Core/Models/InferenceRunCustomPromptEventArgs.cs
new file mode 100644
index 00000000..2c09c8de
--- /dev/null
+++ b/StabilityMatrix.Core/Models/InferenceRunCustomPromptEventArgs.cs
@@ -0,0 +1,14 @@
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+
+namespace StabilityMatrix.Core.Models;
+
+public class InferenceQueueCustomPromptEventArgs : EventArgs
+{
+ public ComfyNodeBuilder Builder { get; } = new();
+
+ public NodeDictionary Nodes => Builder.Nodes;
+
+ public long? SeedOverride { get; init; }
+
+ public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = [];
+}
diff --git a/StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs b/StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs
new file mode 100644
index 00000000..1b682c4a
--- /dev/null
+++ b/StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs
@@ -0,0 +1,36 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using StabilityMatrix.Core.Api;
+using StabilityMatrix.Core.Models.Api.OpenArt;
+using StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Core.Models.PackageModification;
+
+public class DownloadOpenArtWorkflowStep(
+ IOpenArtApi openArtApi,
+ OpenArtSearchResult workflow,
+ ISettingsManager settingsManager
+) : IPackageStep
+{
+ public async Task ExecuteAsync(IProgress? progress = null)
+ {
+ var workflowData = await openArtApi
+ .DownloadWorkflowAsync(new OpenArtDownloadRequest { WorkflowId = workflow.Id })
+ .ConfigureAwait(false);
+
+ var workflowJson = JsonSerializer.SerializeToNode(workflow);
+
+ Directory.CreateDirectory(settingsManager.WorkflowDirectory);
+ var filePath = Path.Combine(settingsManager.WorkflowDirectory, $"{workflowData.Filename}.json");
+
+ var jsonObject = JsonNode.Parse(workflowData.Payload) as JsonObject;
+ jsonObject?.Add("sm_workflow_data", workflowJson);
+
+ await File.WriteAllTextAsync(filePath, JsonSerializer.Serialize(jsonObject)).ConfigureAwait(false);
+
+ progress?.Report(new ProgressReport(1f, "Downloaded OpenArt Workflow"));
+ }
+
+ public string ProgressTitle => "Downloading OpenArt Workflow";
+}
diff --git a/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs b/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs
index ff0d20ae..25c6e357 100644
--- a/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs
+++ b/StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs
@@ -31,7 +31,7 @@ public class InstallPackageStep : IPackageStep
{
void OnConsoleOutput(ProcessOutput output)
{
- progress?.Report(new ProgressReport { IsIndeterminate = true, Message = output.Text });
+ progress?.Report(new ProgressReport { IsIndeterminate = true, ProcessOutput = output });
}
await package
diff --git a/StabilityMatrix.Core/Models/PackageModification/SetupOutputSharingStep.cs b/StabilityMatrix.Core/Models/PackageModification/SetupOutputSharingStep.cs
new file mode 100644
index 00000000..f92ff7ef
--- /dev/null
+++ b/StabilityMatrix.Core/Models/PackageModification/SetupOutputSharingStep.cs
@@ -0,0 +1,15 @@
+using StabilityMatrix.Core.Models.Packages;
+using StabilityMatrix.Core.Models.Progress;
+
+namespace StabilityMatrix.Core.Models.PackageModification;
+
+public class SetupOutputSharingStep(BasePackage package, string installPath) : IPackageStep
+{
+ public Task ExecuteAsync(IProgress? progress = null)
+ {
+ package.SetupOutputFolderLinks(installPath);
+ return Task.CompletedTask;
+ }
+
+ public string ProgressTitle => "Setting up output sharing...";
+}
diff --git a/StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs b/StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs
index 11a8e111..446a49d7 100644
--- a/StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs
+++ b/StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs
@@ -31,7 +31,14 @@ public class UpdatePackageStep : IPackageStep
void OnConsoleOutput(ProcessOutput output)
{
- progress?.Report(new ProgressReport { IsIndeterminate = true, Message = output.Text });
+ progress?.Report(
+ new ProgressReport
+ {
+ IsIndeterminate = true,
+ Message = output.Text,
+ ProcessOutput = output
+ }
+ );
}
var updateResult = await basePackage
diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs
index f823efeb..9635cecc 100644
--- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs
@@ -72,12 +72,12 @@ public class A3WebUI(
public override Dictionary>? SharedOutputFolders =>
new()
{
- [SharedOutputType.Extras] = new[] { "outputs/extras-images" },
+ [SharedOutputType.Extras] = new[] { "output/extras-images" },
[SharedOutputType.Saved] = new[] { "log/images" },
- [SharedOutputType.Img2Img] = new[] { "outputs/img2img-images" },
- [SharedOutputType.Text2Img] = new[] { "outputs/txt2img-images" },
- [SharedOutputType.Img2ImgGrids] = new[] { "outputs/img2img-grids" },
- [SharedOutputType.Text2ImgGrids] = new[] { "outputs/txt2img-grids" }
+ [SharedOutputType.Img2Img] = new[] { "output/img2img-images" },
+ [SharedOutputType.Text2Img] = new[] { "output/txt2img-images" },
+ [SharedOutputType.Img2ImgGrids] = new[] { "output/img2img-grids" },
+ [SharedOutputType.Text2ImgGrids] = new[] { "output/txt2img-grids" }
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
@@ -184,7 +184,7 @@ public class A3WebUI(
public override string MainBranch => "master";
- public override string OutputFolderName => "outputs";
+ public override string OutputFolderName => "output";
public override IPackageExtensionManager ExtensionManager => new A3WebUiExtensionManager(this);
@@ -294,6 +294,11 @@ public class A3WebUI(
VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit);
}
+ public override string? ExtraLaunchArguments { get; set; } =
+ settingsManager.IsLibraryDirSet
+ ? $"--gradio-allowed-path \"{settingsManager.ImagesDirectory}\""
+ : string.Empty;
+
private class A3WebUiExtensionManager(A3WebUI package)
: GitPackageExtensionManager(package.PrerequisiteHelper)
{
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index da47bbb6..c620182c 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -213,7 +213,7 @@ public class ComfyUI(
_
=> pipArgs
.AddArg("--upgrade")
- .WithTorch("~=2.1.0")
+ .WithTorch()
.WithTorchVision()
.WithTorchExtraIndex(
torchVersion switch
@@ -233,9 +233,6 @@ public class ComfyUI(
switch (torchVersion)
{
- case TorchVersion.Cuda:
- pipArgs = pipArgs.WithXFormers("==0.0.22.post4");
- break;
case TorchVersion.Mps:
pipArgs = pipArgs.AddArg("mpmath==1.3.0");
break;
@@ -514,6 +511,32 @@ public class ComfyUI(
}
}
+ ///
+ public override async Task UpdateExtensionAsync(
+ InstalledPackageExtension installedExtension,
+ InstalledPackage installedPackage,
+ PackageExtensionVersion? version = null,
+ IProgress? progress = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ await base.UpdateExtensionAsync(
+ installedExtension,
+ installedPackage,
+ version,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var installedDirs = installedExtension.Paths.OfType().Where(dir => dir.Exists);
+
+ await PostInstallAsync(installedPackage, installedDirs, progress, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
///
public override async Task InstallExtensionAsync(
PackageExtension extension,
@@ -542,6 +565,20 @@ public class ComfyUI(
.Select(path => cloneRoot.JoinDir(path!))
.Where(dir => dir.Exists);
+ await PostInstallAsync(installedPackage, installedDirs, progress, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Runs post install / update tasks (i.e. install.py, requirements.txt)
+ ///
+ private async Task PostInstallAsync(
+ InstalledPackage installedPackage,
+ IEnumerable installedDirs,
+ IProgress? progress = null,
+ CancellationToken cancellationToken = default
+ )
+ {
foreach (var installedDir in installedDirs)
{
cancellationToken.ThrowIfCancellationRequested();
diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs b/StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs
new file mode 100644
index 00000000..13003ca0
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs
@@ -0,0 +1,105 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text.RegularExpressions;
+using JetBrains.Annotations;
+using Semver;
+using StabilityMatrix.Core.Processes;
+
+namespace StabilityMatrix.Core.Models.Packages.Extensions;
+
+///
+/// Extension specifier with optional version constraints.
+///
+[PublicAPI]
+public partial class ExtensionSpecifier
+{
+ public required string Name { get; init; }
+
+ public string? Constraint { get; init; }
+
+ public string? Version { get; init; }
+
+ public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Version;
+
+ public bool TryGetSemVersionRange([NotNullWhen(true)] out SemVersionRange? semVersionRange)
+ {
+ if (!string.IsNullOrEmpty(VersionConstraint))
+ {
+ return SemVersionRange.TryParse(
+ VersionConstraint,
+ SemVersionRangeOptions.Loose,
+ out semVersionRange
+ );
+ }
+
+ semVersionRange = null;
+ return false;
+ }
+
+ public static ExtensionSpecifier Parse(string value)
+ {
+ TryParse(value, true, out var packageSpecifier);
+
+ return packageSpecifier!;
+ }
+
+ public static bool TryParse(string value, [NotNullWhen(true)] out ExtensionSpecifier? extensionSpecifier)
+ {
+ return TryParse(value, false, out extensionSpecifier);
+ }
+
+ private static bool TryParse(
+ string value,
+ bool throwOnFailure,
+ [NotNullWhen(true)] out ExtensionSpecifier? packageSpecifier
+ )
+ {
+ var match = ExtensionSpecifierRegex().Match(value);
+ if (!match.Success)
+ {
+ if (throwOnFailure)
+ {
+ throw new ArgumentException($"Invalid extension specifier: {value}");
+ }
+
+ packageSpecifier = null;
+ return false;
+ }
+
+ packageSpecifier = new ExtensionSpecifier
+ {
+ Name = match.Groups["extension_name"].Value,
+ Constraint = match.Groups["version_constraint"].Value,
+ Version = match.Groups["version"].Value
+ };
+
+ return true;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Name + VersionConstraint;
+ }
+
+ public static implicit operator Argument(ExtensionSpecifier specifier)
+ {
+ return specifier.VersionConstraint is null
+ ? new Argument(specifier.Name)
+ : new Argument((specifier.Name, specifier.VersionConstraint));
+ }
+
+ public static implicit operator ExtensionSpecifier(string specifier)
+ {
+ return Parse(specifier);
+ }
+
+ ///
+ /// Regex to match a pip package specifier.
+ ///
+ [GeneratedRegex(
+ @"(?\S+)\s*(?==|>=|<=|>|<|~=|!=)?\s*(?[a-zA-Z0-9_.]+)?",
+ RegexOptions.CultureInvariant,
+ 5000
+ )]
+ private static partial Regex ExtensionSpecifierRegex();
+}
diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs
index 1aac49f1..3cc5e17b 100644
--- a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs
+++ b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs
@@ -201,6 +201,31 @@ public abstract partial class GitPackageExtensionManager(IPrerequisiteHelper pre
return extensions;
}
+ public virtual async Task GetInstalledExtensionInfoAsync(
+ InstalledPackageExtension installedExtension
+ )
+ {
+ if (installedExtension.PrimaryPath is not DirectoryPath extensionDirectory)
+ {
+ return installedExtension;
+ }
+
+ // Get git version
+ var version = await prerequisiteHelper
+ .GetGitRepositoryVersion(extensionDirectory)
+ .ConfigureAwait(false);
+
+ return installedExtension with
+ {
+ Version = new PackageExtensionVersion
+ {
+ Tag = version.Tag,
+ Branch = version.Branch,
+ CommitSha = version.CommitSha
+ }
+ };
+ }
+
///
public virtual async Task InstallExtensionAsync(
PackageExtension extension,
diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs b/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs
index 76903233..67131238 100644
--- a/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs
+++ b/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs
@@ -89,6 +89,21 @@ public interface IPackageExtensionManager
CancellationToken cancellationToken = default
);
+ ///
+ /// Like , but does not check version.
+ ///
+ Task> GetInstalledExtensionsLiteAsync(
+ InstalledPackage installedPackage,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Get updated info (version) for an installed extension.
+ ///
+ Task GetInstalledExtensionInfoAsync(
+ InstalledPackageExtension installedExtension
+ );
+
///
/// Install an extension to the provided package.
///
diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
index 1163b729..594f95ec 100644
--- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
+++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
@@ -132,32 +132,19 @@ public class KohyaSs(
await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false);
// Extra dep needed before running setup since v23.0.x
- await venvRunner.PipInstall("packaging").ConfigureAwait(false);
+ await venvRunner.PipInstall(["rich", "packaging"]).ConfigureAwait(false);
if (Compat.IsWindows)
{
- var setupSmPath = Path.Combine(installLocation, "setup", "setup_sm.py");
- var setupText = """
- import setup_windows
- import setup_common
-
- setup_common.install_requirements('requirements_windows_torch2.txt', check_no_verify_flag=False)
- setup_windows.sync_bits_and_bytes_files()
- setup_common.configure_accelerate(run_accelerate=False)
- """;
- await File.WriteAllTextAsync(setupSmPath, setupText).ConfigureAwait(false);
-
// Install
- await venvRunner.CustomInstall("setup/setup_sm.py", onConsoleOutput).ConfigureAwait(false);
- await venvRunner.PipInstall("bitsandbytes-windows").ConfigureAwait(false);
+ await venvRunner
+ .CustomInstall("setup/setup_windows.py --headless", onConsoleOutput)
+ .ConfigureAwait(false);
}
else if (Compat.IsLinux)
{
await venvRunner
- .CustomInstall(
- "setup/setup_linux.py --platform-requirements-file=requirements_linux.txt --no_run_accelerate",
- onConsoleOutput
- )
+ .CustomInstall("setup/setup_linux.py --headless", onConsoleOutput)
.ConfigureAwait(false);
}
}
@@ -212,6 +199,7 @@ public class KohyaSs(
var kohyaGuiDir = Path.Combine(installedPackagePath, "kohya_gui");
var guiDirExists = Directory.Exists(kohyaGuiDir);
var filesToUpdate = new List();
+
if (guiDirExists)
{
filesToUpdate.AddRange(
diff --git a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
index 181abc32..96dc5d65 100644
--- a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
+++ b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
@@ -124,10 +124,10 @@ public class RuinedFooocus(
await venvRunner
.PipInstall(
new PipInstallArgs()
- .WithTorch("==2.0.1")
- .WithTorchVision("==0.15.2")
- .WithXFormers("==0.0.20")
- .WithTorchExtraIndex("cu118")
+ .WithTorch("==2.1.2")
+ .WithTorchVision("==0.16.2")
+ .WithXFormers("==0.0.23.post1")
+ .WithTorchExtraIndex("cu121")
.WithParsedFromRequirementsTxt(
await requirements.ReadAllTextAsync().ConfigureAwait(false),
excludePattern: "torch"
diff --git a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs
index a14c13fc..f64a4197 100644
--- a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs
+++ b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs
@@ -35,12 +35,12 @@ public class SDWebForge(
"https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/ca5e05ed-bd86-4ced-8662-f41034648e8c"
);
- public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended;
-
public override string MainBranch => "main";
public override bool ShouldIgnoreReleases => true;
public override IPackageExtensionManager ExtensionManager => null;
public override string OutputFolderName => "output";
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended;
+
public override Dictionary>? SharedOutputFolders =>
new()
{
@@ -178,9 +178,4 @@ public class SDWebForge(
await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false));
}
-
- public override string? ExtraLaunchArguments { get; set; } =
- settingsManager.IsLibraryDirSet
- ? $"--gradio-allowed-path \"{settingsManager.ImagesDirectory}\""
- : string.Empty;
}
diff --git a/StabilityMatrix.Core/Models/Progress/ProgressReport.cs b/StabilityMatrix.Core/Models/Progress/ProgressReport.cs
index 22657d70..6ffcec6d 100644
--- a/StabilityMatrix.Core/Models/Progress/ProgressReport.cs
+++ b/StabilityMatrix.Core/Models/Progress/ProgressReport.cs
@@ -1,4 +1,6 @@
-namespace StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Processes;
+
+namespace StabilityMatrix.Core.Models.Progress;
public record struct ProgressReport
{
@@ -6,21 +8,30 @@ public record struct ProgressReport
/// Progress value as percentage between 0 and 1.
///
public double? Progress { get; init; } = 0;
+
///
/// Current progress count.
///
public ulong? Current { get; init; } = 0;
+
///
/// Total progress count.
///
public ulong? Total { get; init; } = 0;
public string? Title { get; init; }
public string? Message { get; init; }
+ public ProcessOutput? ProcessOutput { get; init; }
public bool IsIndeterminate { get; init; } = false;
- public float Percentage => (float) Math.Ceiling(Math.Clamp(Progress ?? 0, 0, 1) * 100);
+ public float Percentage => (float)Math.Ceiling(Math.Clamp(Progress ?? 0, 0, 1) * 100);
public ProgressType Type { get; init; } = ProgressType.Generic;
-
- public ProgressReport(double progress, string? title = null, string? message = null, bool isIndeterminate = false, ProgressType type = ProgressType.Generic)
+
+ public ProgressReport(
+ double progress,
+ string? title = null,
+ string? message = null,
+ bool isIndeterminate = false,
+ ProgressType type = ProgressType.Generic
+ )
{
Progress = progress;
Title = title;
@@ -28,32 +39,53 @@ public record struct ProgressReport
IsIndeterminate = isIndeterminate;
Type = type;
}
-
- public ProgressReport(ulong current, ulong total, string? title = null, string? message = null, bool isIndeterminate = false, ProgressType type = ProgressType.Generic)
+
+ public ProgressReport(
+ ulong current,
+ ulong total,
+ string? title = null,
+ string? message = null,
+ bool isIndeterminate = false,
+ ProgressType type = ProgressType.Generic
+ )
{
Current = current;
Total = total;
- Progress = (double) current / total;
+ Progress = (double)current / total;
Title = title;
Message = message;
IsIndeterminate = isIndeterminate;
Type = type;
}
-
- public ProgressReport(int current, int total, string? title = null, string? message = null, bool isIndeterminate = false, ProgressType type = ProgressType.Generic)
+
+ public ProgressReport(
+ int current,
+ int total,
+ string? title = null,
+ string? message = null,
+ bool isIndeterminate = false,
+ ProgressType type = ProgressType.Generic
+ )
{
- if (current < 0) throw new ArgumentOutOfRangeException(nameof(current), "Current progress cannot negative.");
- if (total < 0) throw new ArgumentOutOfRangeException(nameof(total), "Total progress cannot be negative.");
- Current = (ulong) current;
- Total = (ulong) total;
- Progress = (double) current / total;
+ if (current < 0)
+ throw new ArgumentOutOfRangeException(nameof(current), "Current progress cannot negative.");
+ if (total < 0)
+ throw new ArgumentOutOfRangeException(nameof(total), "Total progress cannot be negative.");
+ Current = (ulong)current;
+ Total = (ulong)total;
+ Progress = (double)current / total;
Title = title;
Message = message;
IsIndeterminate = isIndeterminate;
Type = type;
}
-
- public ProgressReport(ulong current, string? title = null, string? message = null, ProgressType type = ProgressType.Generic)
+
+ public ProgressReport(
+ ulong current,
+ string? title = null,
+ string? message = null,
+ ProgressType type = ProgressType.Generic
+ )
{
Current = current;
Title = title;
@@ -61,6 +93,6 @@ public record struct ProgressReport
IsIndeterminate = true;
Type = type;
}
-
+
// Implicit conversion from action
}
diff --git a/StabilityMatrix.Core/Models/Settings/Settings.cs b/StabilityMatrix.Core/Models/Settings/Settings.cs
index 50f34f11..01e22fd2 100644
--- a/StabilityMatrix.Core/Models/Settings/Settings.cs
+++ b/StabilityMatrix.Core/Models/Settings/Settings.cs
@@ -34,6 +34,19 @@ public class Settings
set => ActiveInstalledPackageId = value?.Id;
}
+ [JsonPropertyName("PreferredWorkflowPackage")]
+ public Guid? PreferredWorkflowPackageId { get; set; }
+
+ [JsonIgnore]
+ public InstalledPackage? PreferredWorkflowPackage
+ {
+ get =>
+ PreferredWorkflowPackageId == null
+ ? null
+ : InstalledPackages.FirstOrDefault(x => x.Id == PreferredWorkflowPackageId);
+ set => PreferredWorkflowPackageId = value?.Id;
+ }
+
public bool HasSeenWelcomeNotification { get; set; }
public List? PathExtensions { get; set; }
public string? WebApiHost { get; set; }
@@ -97,7 +110,18 @@ public class Settings
public bool IsDiscordRichPresenceEnabled { get; set; }
- public Dictionary? EnvironmentVariables { get; set; }
+ [JsonIgnore]
+ public Dictionary DefaultEnvironmentVariables { get; } =
+ new() { ["SETUPTOOLS_USE_DISTUTILS"] = "stdlib" };
+
+ [JsonPropertyName("EnvironmentVariables")]
+ public Dictionary? UserEnvironmentVariables { get; set; }
+
+ [JsonIgnore]
+ public IReadOnlyDictionary EnvironmentVariables =>
+ DefaultEnvironmentVariables
+ .Concat(UserEnvironmentVariables ?? [])
+ .ToDictionary(x => x.Key, x => x.Value);
public HashSet? InstalledModelHashes { get; set; } = new();
@@ -120,6 +144,8 @@ public class Settings
public Size InferenceImageSize { get; set; } = new(150, 190);
public Size OutputsImageSize { get; set; } = new(300, 300);
public HolidayMode HolidayModeSetting { get; set; } = HolidayMode.Automatic;
+ public bool IsWorkflowInfiniteScrollEnabled { get; set; } = true;
+ public bool IsOutputsTreeViewEnabled { get; set; } = true;
[JsonIgnore]
public bool IsHolidayModeActive =>
diff --git a/StabilityMatrix.Core/Models/Settings/TeachingTip.cs b/StabilityMatrix.Core/Models/Settings/TeachingTip.cs
index 756e6cce..d69595c7 100644
--- a/StabilityMatrix.Core/Models/Settings/TeachingTip.cs
+++ b/StabilityMatrix.Core/Models/Settings/TeachingTip.cs
@@ -13,6 +13,8 @@ public record TeachingTip(string Value) : StringValue(Value)
public static TeachingTip CheckpointCategoriesTip => new("CheckpointCategoriesTip");
public static TeachingTip PackageExtensionsInstallNotice => new("PackageExtensionsInstallNotice");
public static TeachingTip DownloadsTip => new("DownloadsTip");
+ public static TeachingTip WebUiButtonMovedTip => new("WebUiButtonMovedTip");
+ public static TeachingTip InferencePromptHelpButtonTip => new("InferencePromptHelpButtonTip");
///
public override string ToString()
diff --git a/StabilityMatrix.Core/Python/PyVenvRunner.cs b/StabilityMatrix.Core/Python/PyVenvRunner.cs
index 2266498b..5dfc3a03 100644
--- a/StabilityMatrix.Core/Python/PyVenvRunner.cs
+++ b/StabilityMatrix.Core/Python/PyVenvRunner.cs
@@ -214,7 +214,7 @@ public class PyVenvRunner : IDisposable, IAsyncDisposable
});
SetPyvenvCfg(PyRunner.PythonDir);
- RunDetached(args.Prepend("-m pip install"), outputAction);
+ RunDetached(args.Prepend("-m pip install").Concat("--exists-action s"), outputAction);
await Process.WaitForExitAsync().ConfigureAwait(false);
// Check return code
diff --git a/StabilityMatrix.Core/Services/ISettingsManager.cs b/StabilityMatrix.Core/Services/ISettingsManager.cs
index a070fead..a1537f77 100644
--- a/StabilityMatrix.Core/Services/ISettingsManager.cs
+++ b/StabilityMatrix.Core/Services/ISettingsManager.cs
@@ -22,6 +22,7 @@ public interface ISettingsManager
Settings Settings { get; }
List PackageInstallsInProgress { get; set; }
+ DirectoryPath WorkflowDirectory { get; }
///
/// Event fired when the library directory is changed
diff --git a/StabilityMatrix.Core/Services/MetadataImportService.cs b/StabilityMatrix.Core/Services/MetadataImportService.cs
index d31a603d..21ce5a2c 100644
--- a/StabilityMatrix.Core/Services/MetadataImportService.cs
+++ b/StabilityMatrix.Core/Services/MetadataImportService.cs
@@ -107,7 +107,9 @@ public class MetadataImportService(
.ConfigureAwait(false);
var image = modelVersion.Images?.FirstOrDefault(
- img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
+ img =>
+ LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
+ && img.Type == "image"
);
if (image == null)
{
@@ -201,7 +203,9 @@ public class MetadataImportService(
.ConfigureAwait(false);
var image = modelVersion.Images?.FirstOrDefault(
- img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
+ img =>
+ LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
+ && img.Type == "image"
);
if (image == null)
continue;
@@ -264,7 +268,9 @@ public class MetadataImportService(
.ConfigureAwait(false);
var image = modelVersion.Images?.FirstOrDefault(
- img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
+ img =>
+ LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
+ && img.Type == "image"
);
if (image == null)
diff --git a/StabilityMatrix.Core/Services/SettingsManager.cs b/StabilityMatrix.Core/Services/SettingsManager.cs
index b7441cf4..76fce343 100644
--- a/StabilityMatrix.Core/Services/SettingsManager.cs
+++ b/StabilityMatrix.Core/Services/SettingsManager.cs
@@ -64,6 +64,7 @@ public class SettingsManager : ISettingsManager
private FilePath SettingsFile => LibraryDir.JoinFile("settings.json");
public string ModelsDirectory => Path.Combine(LibraryDir, "Models");
public string DownloadsDirectory => Path.Combine(LibraryDir, ".downloads");
+ public DirectoryPath WorkflowDirectory => LibraryDir.JoinDir("Workflows");
public DirectoryPath TagsDirectory => LibraryDir.JoinDir("Tags");
public DirectoryPath ImagesDirectory => LibraryDir.JoinDir("Images");
public DirectoryPath ImagesInferenceDirectory => ImagesDirectory.JoinDir("Inference");
diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
index de135217..5005b1bd 100644
--- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj
+++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj
@@ -17,8 +17,8 @@
-
-
+
+
@@ -27,10 +27,10 @@
-
+
-
+
@@ -45,7 +45,7 @@
-
+
@@ -53,7 +53,7 @@
-
+
diff --git a/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs b/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs
index 4163922d..b4920bdb 100644
--- a/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs
+++ b/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs
@@ -8,31 +8,28 @@ public class PackageFactoryTests
{
private PackageFactory packageFactory = null!;
private IEnumerable fakeBasePackages = null!;
-
+
[TestInitialize]
public void Setup()
{
- fakeBasePackages = new List
- {
- new DankDiffusion(null!, null!, null!, null!)
- };
- packageFactory = new PackageFactory(fakeBasePackages);
+ fakeBasePackages = new List { new DankDiffusion(null!, null!, null!, null!) };
+ packageFactory = new PackageFactory(fakeBasePackages, null, null, null, null, null);
}
-
+
[TestMethod]
public void GetAllAvailablePackages_ReturnsAllPackages()
{
var result = packageFactory.GetAllAvailablePackages();
Assert.AreEqual(1, result.Count());
}
-
+
[TestMethod]
public void FindPackageByName_ReturnsPackage()
{
var result = packageFactory.FindPackageByName("dank-diffusion");
Assert.IsNotNull(result);
}
-
+
[TestMethod]
public void FindPackageByName_ReturnsNull()
{
diff --git a/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj b/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj
index 6283704a..257b8f93 100644
--- a/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj
+++ b/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj
@@ -17,18 +17,18 @@
-
+
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj b/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj
index d4c1e79e..eb592e08 100644
--- a/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj
+++ b/StabilityMatrix.UITests/StabilityMatrix.UITests.csproj
@@ -15,16 +15,16 @@
-
+
-
-
+
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive