diff --git a/CHANGELOG.md b/CHANGELOG.md index 043ccb1c..fa0e8fc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,18 @@ 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-preview.1 +## 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 + +## v2.10.0-pre.1 ### Added - Added OpenArt.AI workflow browser for ComfyUI workflows - Added Output Sharing toggle in Advanced Options during install flow diff --git a/StabilityMatrix.Avalonia/Assets/sitecustomize.py b/StabilityMatrix.Avalonia/Assets/sitecustomize.py index b6ebb4c1..d154c13a 100644 --- a/StabilityMatrix.Avalonia/Assets/sitecustomize.py +++ b/StabilityMatrix.Avalonia/Assets/sitecustomize.py @@ -68,6 +68,20 @@ def _patch_rich_console(): pass except Exception as e: print("[sitecustomize error]:", e) + + try: + from pip._vendor.rich import console + + class _Console(console.Console): + @property + def is_terminal(self) -> bool: + return True + + console.Console = _Console + except ImportError: + pass + except Exception as e: + print("[sitecustomize error]:", e) _patch_rich_console() diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 3fc6455b..471f4a53 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -360,7 +360,7 @@ public static class DesignData { new() { - Nsfw = "None", + NsfwLevel = 1, Url = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/" + "78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg" 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/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/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/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs index bcb2ae62..341a3913 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; @@ -159,7 +160,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel // Try to find a valid image var image = images ?.Where(img => LocalModelFile.SupportedImageExtensions.Any(img.Url.Contains)) - .FirstOrDefault(image => nsfwEnabled || image.Nsfw == "None"); + .FirstOrDefault(image => nsfwEnabled || image.NsfwLevel <= 1); if (image != null) { CardImage = new Uri(image.Url); @@ -256,7 +257,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); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs index c8592cc7..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; @@ -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/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs index 9ef4c378..0b84de65 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.Nsfw == "None") + ?.Where(img => nsfwEnabled || img.NsfwLevel <= 2) ?.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/InstalledWorkflowsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs index b765c797..0bc14b2c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs @@ -59,6 +59,11 @@ public partial class InstalledWorkflowsViewModel( { workflowsCache.Clear(); + if (!Directory.Exists(settingsManager.WorkflowDirectory)) + { + Directory.CreateDirectory(settingsManager.WorkflowDirectory); + } + foreach ( var workflowPath in Directory.EnumerateFiles( settingsManager.WorkflowDirectory, diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs index 4e9b7fa2..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; @@ -188,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(); @@ -252,6 +254,7 @@ public partial class PackageInstallDetailViewModel( setPackageInstallingStep, prereqStep, downloadStep, + unpackSiteCustomizeStep, installStep, setupModelFoldersStep, addInstalledPackageStep 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 index 38011e04..73f89a8b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs @@ -104,6 +104,14 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I } } + [RelayCommand] + private async Task Restart() + { + await Stop(); + await Task.Delay(100); + LaunchPackage(); + } + [RelayCommand] private void LaunchPackage() { @@ -113,10 +121,10 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I [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(); - IsRunning = false; } [RelayCommand] diff --git a/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml index 7d86b568..d44b44e6 100644 --- a/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml +++ b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml @@ -34,8 +34,16 @@ Command="{Binding LaunchPackageCommand}" VerticalAlignment="Center" Label="{x:Static lang:Resources.Action_Launch}" /> + - + - + + + + + + + + + + + + + + + + ? 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/Models/Api/CivitImage.cs b/StabilityMatrix.Core/Models/Api/CivitImage.cs index 0cab1b50..82aadce2 100644 --- a/StabilityMatrix.Core/Models/Api/CivitImage.cs +++ b/StabilityMatrix.Core/Models/Api/CivitImage.cs @@ -6,18 +6,18 @@ public class CivitImage { [JsonPropertyName("url")] public string Url { get; set; } - - [JsonPropertyName("nsfw")] - public string Nsfw { get; set; } - + + [JsonPropertyName("nsfwLevel")] + public int? NsfwLevel { get; set; } + [JsonPropertyName("width")] public int Width { get; set; } - + [JsonPropertyName("height")] public int Height { get; set; } - + [JsonPropertyName("hash")] public string Hash { get; set; } - + // TODO: "meta" ( object? ) } 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/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/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 }