diff --git a/CHANGELOG.md b/CHANGELOG.md index fa0e8fc8..38097f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - 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 diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index 19eb7989..2ca1c5c3 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -81,6 +81,7 @@ + + 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/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 471f4a53..c61d19ba 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -923,6 +923,9 @@ 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 diff --git a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs index f4bd13fb..487d39b7 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs @@ -28,6 +28,16 @@ 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 . /// 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/Inference/InferenceImageToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs index 77c5230f..7600384a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs @@ -56,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(); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs index a1924559..04e46f05 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs @@ -131,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( @@ -145,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/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/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs index 8890a514..664ab0c8 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs @@ -109,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) + ]; }); } diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index c9a378b6..df14284a 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -383,6 +383,45 @@ public class ComfyNodeBuilder 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"); @@ -867,7 +906,26 @@ public class ComfyNodeBuilder set => SamplerTemporaryArgs["Base"] = value; } - public PrimaryNodeConnection? Primary { get; set; } + /// + /// 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/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/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 19843e75..c620182c 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -511,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, @@ -539,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();