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();