diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6633e0a64..8145f5f53 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ 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.4.4
+### Added
+- Added button to toggle automatic scrolling of console output
+### Fixed
+- Fixed [#130](https://github.com/LykosAI/StabilityMatrix/issues/130) ComfyUI extra_model_paths.yaml file being overwritten on each launch
+- Fixed some package updates not showing any console output
+- Fixed auto-close of update dialog when package update is complete
+
## v2.4.3
### Added
- Added "--no-download-sd-model" launch argument option for Stable Diffusion Web UI
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
index 81aa8822e..5cbc24b53 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
+++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
@@ -464,6 +464,15 @@ public static string Label_Appearance {
}
}
+ ///
+ /// Looks up a localized string similar to Automatically scroll to end of console output.
+ ///
+ public static string Label_AutoScrollToEnd {
+ get {
+ return ResourceManager.GetString("Label_AutoScrollToEnd", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Base Model.
///
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx
index 4b09fa284..88399d11f 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.resx
@@ -639,4 +639,7 @@
Branch
+
+ Automatically scroll to end of console output
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs
index 3905cfe1e..076ef466e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs
@@ -86,6 +86,9 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
[ObservableProperty]
private BasePackage? runningPackage;
+ [ObservableProperty]
+ private bool autoScrollToEnd;
+
public virtual BasePackage? SelectedBasePackage =>
PackageFactory.FindPackageByName(SelectedPackage?.PackageName);
@@ -126,6 +129,12 @@ ServiceManager dialogFactory
settings => settings.ActiveInstalledPackage
);
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.AutoScrollToEnd,
+ settings => settings.AutoScrollLaunchConsoleToEnd
+ );
+
EventManager.Instance.PackageLaunchRequested += OnPackageLaunchRequested;
EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;
EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;
@@ -161,6 +170,14 @@ private void OnPackageLaunchRequested(object? sender, Guid e)
LaunchAsync().SafeFireAndForget();
}
+ partial void OnAutoScrollToEndChanged(bool value)
+ {
+ if (value)
+ {
+ EventManager.Instance.OnScrollToBottomRequested();
+ }
+ }
+
public override void OnLoaded()
{
// Ensure active package either exists or is null
@@ -179,6 +196,7 @@ public override void OnLoaded()
// Load active package
SelectedPackage = settingsManager.Settings.ActiveInstalledPackage;
+ AutoScrollToEnd = settingsManager.Settings.AutoScrollLaunchConsoleToEnd;
}
[RelayCommand]
@@ -487,7 +505,11 @@ private void OnProcessExited(object? sender, int exitCode)
private void OnProcessOutputReceived(ProcessOutput output)
{
Console.Post(output);
- EventManager.Instance.OnScrollToBottomRequested();
+
+ if (AutoScrollToEnd)
+ {
+ EventManager.Instance.OnScrollToBottomRequested();
+ }
}
private void OnOneClickInstallFinished(object? sender, bool e)
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
index d0044144c..865e998de 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
@@ -143,12 +143,16 @@ public async Task ShowInstallDialog()
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps.ToList());
- EventManager.Instance.OnInstalledPackagesChanged();
- notificationService.Show(
- "Package Install Complete",
- $"{viewModel.InstallName} installed successfully",
- NotificationType.Success
- );
+
+ if (!runner.Failed)
+ {
+ EventManager.Instance.OnInstalledPackagesChanged();
+ notificationService.Show(
+ "Package Install Complete",
+ $"{viewModel.InstallName} installed successfully",
+ NotificationType.Success
+ );
+ }
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs
index 70dadda1b..dc410872a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs
@@ -42,6 +42,7 @@ private void PackageModificationRunnerOnProgressChanged(object? sender, Progress
Progress.IsIndeterminate = e.IsIndeterminate;
Progress.Text = packageModificationRunner.CurrentStep?.ProgressTitle;
Name = packageModificationRunner.CurrentStep?.ProgressTitle;
+ Failed = packageModificationRunner.Failed;
if (string.IsNullOrWhiteSpace(e.Message) || e.Message.Contains("Downloading..."))
return;
@@ -51,12 +52,20 @@ private void PackageModificationRunnerOnProgressChanged(object? sender, Progress
if (
e is { Message: not null, Percentage: >= 100 }
- && e.Message.Contains("Package Install Complete")
+ && e.Message.Contains(
+ packageModificationRunner.ModificationCompleteMessage ?? "Package Install Complete"
+ )
&& Progress.CloseWhenFinished
)
{
Dispatcher.UIThread.Post(() => dialog?.Hide());
}
+
+ if (Failed)
+ {
+ Progress.Text = "Package Modification Failed";
+ Name = "Package Modification Failed";
+ }
}
public async Task ShowProgressDialog()
diff --git a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml
index a1f2e330b..1d5f4ec6d 100644
--- a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml
+++ b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml
@@ -12,6 +12,7 @@
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
+ xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
d:DataContext="{x:Static mocks:DesignData.LaunchPageViewModel}"
d:DesignHeight="450"
d:DesignWidth="700"
@@ -25,7 +26,7 @@
-
@@ -145,6 +146,18 @@
+
+
+
+ ConsoleOutput { get; }
Guid Id { get; }
bool ShowDialogOnStart { get; init; }
+ string? ModificationCompleteMessage { get; init; }
+ bool Failed { get; set; }
}
diff --git a/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs b/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs
index 034bf1d39..c546671e3 100644
--- a/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs
+++ b/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs
@@ -21,16 +21,35 @@ public async Task ExecuteSteps(IReadOnlyList steps)
foreach (var step in steps)
{
CurrentStep = step;
- await step.ExecuteAsync(progress).ConfigureAwait(false);
+ try
+ {
+ await step.ExecuteAsync(progress).ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ progress.Report(
+ new ProgressReport(
+ 1f,
+ title: "Error modifying package",
+ message: $"Error: {e}",
+ isIndeterminate: false
+ )
+ );
+ Failed = true;
+ break;
+ }
}
- progress.Report(
- new ProgressReport(
- 1f,
- message: ModificationCompleteMessage ?? "Package Install Complete",
- isIndeterminate: false
- )
- );
+ if (!Failed)
+ {
+ progress.Report(
+ new ProgressReport(
+ 1f,
+ message: ModificationCompleteMessage ?? "Package Install Complete",
+ isIndeterminate: false
+ )
+ );
+ }
IsRunning = false;
}
@@ -39,6 +58,7 @@ public async Task ExecuteSteps(IReadOnlyList steps)
public bool ShowDialogOnStart { get; init; }
public bool IsRunning { get; set; }
+ public bool Failed { get; set; }
public ProgressReport CurrentProgress { get; set; }
public IPackageStep? CurrentStep { get; set; }
public List ConsoleOutput { get; } = new();
diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
index b6d50e189..e88e2d80d 100644
--- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
+++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
@@ -290,7 +290,7 @@ await DownloadPackage(
)
.ConfigureAwait(false);
- await InstallPackage(installedPackage.FullPath, torchVersion, progress)
+ await InstallPackage(installedPackage.FullPath, torchVersion, progress, onConsoleOutput)
.ConfigureAwait(false);
return new InstalledPackageVersion { InstalledReleaseVersion = latestRelease.TagName };
@@ -312,7 +312,7 @@ await DownloadPackage(
progress
)
.ConfigureAwait(false);
- await InstallPackage(installedPackage.FullPath, torchVersion, progress)
+ await InstallPackage(installedPackage.FullPath, torchVersion, progress, onConsoleOutput)
.ConfigureAwait(false);
return new InstalledPackageVersion
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index 1c30940be..520d35586 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -9,8 +9,10 @@
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
+using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
+using YamlDotNet.Serialization.TypeInspectors;
namespace StabilityMatrix.Core.Models.Packages;
@@ -233,11 +235,6 @@ SharedFolderMethod sharedFolderMethod
var extraPathsYamlPath = installDirectory + "extra_model_paths.yaml";
var modelsDir = SettingsManager.ModelsDirectory;
- var deserializer = new DeserializerBuilder()
- .WithNamingConvention(UnderscoredNamingConvention.Instance)
- .IgnoreUnmatchedProperties()
- .Build();
-
var exists = File.Exists(extraPathsYamlPath);
if (!exists)
{
@@ -245,34 +242,87 @@ SharedFolderMethod sharedFolderMethod
File.WriteAllText(extraPathsYamlPath, string.Empty);
}
var yaml = File.ReadAllText(extraPathsYamlPath);
- var comfyModelPaths =
- deserializer.Deserialize(yaml)
- ??
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
- // cuz it can actually be null lol
- new ComfyModelPathsYaml();
-
- comfyModelPaths.StabilityMatrix ??= new ComfyModelPathsYaml.SmData();
- comfyModelPaths.StabilityMatrix.Checkpoints = Path.Combine(modelsDir, "StableDiffusion");
- comfyModelPaths.StabilityMatrix.Vae = Path.Combine(modelsDir, "VAE");
- comfyModelPaths.StabilityMatrix.Loras =
- $"{Path.Combine(modelsDir, "Lora")}\n" + $"{Path.Combine(modelsDir, "LyCORIS")}";
- comfyModelPaths.StabilityMatrix.UpscaleModels =
- $"{Path.Combine(modelsDir, "ESRGAN")}\n"
- + $"{Path.Combine(modelsDir, "RealESRGAN")}\n"
- + $"{Path.Combine(modelsDir, "SwinIR")}";
- comfyModelPaths.StabilityMatrix.Embeddings = Path.Combine(modelsDir, "TextualInversion");
- comfyModelPaths.StabilityMatrix.Hypernetworks = Path.Combine(modelsDir, "Hypernetwork");
- comfyModelPaths.StabilityMatrix.Controlnet = Path.Combine(modelsDir, "ControlNet");
- comfyModelPaths.StabilityMatrix.Clip = Path.Combine(modelsDir, "CLIP");
- comfyModelPaths.StabilityMatrix.Diffusers = Path.Combine(modelsDir, "Diffusers");
- comfyModelPaths.StabilityMatrix.Gligen = Path.Combine(modelsDir, "GLIGEN");
- comfyModelPaths.StabilityMatrix.VaeApprox = Path.Combine(modelsDir, "ApproxVAE");
+ using var sr = new StringReader(yaml);
+ var yamlStream = new YamlStream();
+ yamlStream.Load(sr);
+
+ if (!yamlStream.Documents.Any())
+ {
+ yamlStream.Documents.Add(new YamlDocument(new YamlMappingNode()));
+ }
+
+ var root = yamlStream.Documents[0].RootNode;
+ if (root is not YamlMappingNode mappingNode)
+ {
+ throw new Exception("Invalid extra_model_paths.yaml");
+ }
+ // check if we have a child called "stability_matrix"
+ var stabilityMatrixNode = mappingNode.Children.FirstOrDefault(
+ c => c.Key.ToString() == "stability_matrix"
+ );
+
+ if (stabilityMatrixNode.Key != null)
+ {
+ if (stabilityMatrixNode.Value is not YamlMappingNode nodeValue)
+ return Task.CompletedTask;
+
+ nodeValue.Children["checkpoints"] = Path.Combine(modelsDir, "StableDiffusion");
+ nodeValue.Children["vae"] = Path.Combine(modelsDir, "VAE");
+ nodeValue.Children["loras"] =
+ $"{Path.Combine(modelsDir, "Lora")}\n" + $"{Path.Combine(modelsDir, "LyCORIS")}";
+ nodeValue.Children["upscale_models"] =
+ $"{Path.Combine(modelsDir, "ESRGAN")}\n"
+ + $"{Path.Combine(modelsDir, "RealESRGAN")}\n"
+ + $"{Path.Combine(modelsDir, "SwinIR")}";
+ nodeValue.Children["embeddings"] = Path.Combine(modelsDir, "TextualInversion");
+ nodeValue.Children["hypernetworks"] = Path.Combine(modelsDir, "Hypernetwork");
+ nodeValue.Children["controlnet"] = Path.Combine(modelsDir, "ControlNet");
+ nodeValue.Children["clip"] = Path.Combine(modelsDir, "CLIP");
+ nodeValue.Children["diffusers"] = Path.Combine(modelsDir, "Diffusers");
+ nodeValue.Children["gligen"] = Path.Combine(modelsDir, "GLIGEN");
+ nodeValue.Children["vae_approx"] = Path.Combine(modelsDir, "ApproxVAE");
+ }
+ else
+ {
+ stabilityMatrixNode = new KeyValuePair(
+ new YamlScalarNode("stability_matrix"),
+ new YamlMappingNode
+ {
+ { "checkpoints", Path.Combine(modelsDir, "StableDiffusion") },
+ { "vae", Path.Combine(modelsDir, "VAE") },
+ {
+ "loras",
+ $"{Path.Combine(modelsDir, "Lora")}\n{Path.Combine(modelsDir, "LyCORIS")}"
+ },
+ {
+ "upscale_models",
+ $"{Path.Combine(modelsDir, "ESRGAN")}\n{Path.Combine(modelsDir, "RealESRGAN")}\n{Path.Combine(modelsDir, "SwinIR")}"
+ },
+ { "embeddings", Path.Combine(modelsDir, "TextualInversion") },
+ { "hypernetworks", Path.Combine(modelsDir, "Hypernetwork") },
+ { "controlnet", Path.Combine(modelsDir, "ControlNet") },
+ { "clip", Path.Combine(modelsDir, "CLIP") },
+ { "diffusers", Path.Combine(modelsDir, "Diffusers") },
+ { "gligen", Path.Combine(modelsDir, "GLIGEN") },
+ { "vae_approx", Path.Combine(modelsDir, "ApproxVAE") }
+ }
+ );
+ }
+
+ var newRootNode = new YamlMappingNode();
+ foreach (
+ var child in mappingNode.Children.Where(c => c.Key.ToString() != "stability_matrix")
+ )
+ {
+ newRootNode.Children.Add(child);
+ }
+
+ newRootNode.Children.Add(stabilityMatrixNode);
var serializer = new SerializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.Build();
- var yamlData = serializer.Serialize(comfyModelPaths);
+ var yamlData = serializer.Serialize(newRootNode);
File.WriteAllText(extraPathsYamlPath, yamlData);
return Task.CompletedTask;
diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs
index f5026ff12..c564d3f8a 100644
--- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs
+++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs
@@ -85,11 +85,9 @@ IPrerequisiteHelper prerequisiteHelper
public override IEnumerable AvailableTorchVersions =>
new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.Rocm };
- public override async Task GetLatestVersion()
- {
- var release = await GetLatestRelease().ConfigureAwait(false);
- return release.TagName!;
- }
+ public override Task GetLatestVersion() => Task.FromResult("main");
+
+ public override bool ShouldIgnoreReleases => true;
public override async Task InstallPackage(
string installLocation,
diff --git a/StabilityMatrix.Core/Models/Settings/Settings.cs b/StabilityMatrix.Core/Models/Settings/Settings.cs
index 4c6edb9a2..d2e667bca 100644
--- a/StabilityMatrix.Core/Models/Settings/Settings.cs
+++ b/StabilityMatrix.Core/Models/Settings/Settings.cs
@@ -56,6 +56,8 @@ public InstalledPackage? ActiveInstalledPackage
public float AnimationScale { get; set; } = 1.0f;
+ public bool AutoScrollLaunchConsoleToEnd { get; set; } = true;
+
public void RemoveInstalledPackageAndUpdateActive(InstalledPackage package)
{
RemoveInstalledPackageAndUpdateActive(package.Id);