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