From 49273b049a143bb4ba8d5979b9b9ea3bf6db1f62 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 25 Feb 2024 16:54:25 -0800 Subject: [PATCH 01/94] wip start of multi-package running stuff --- .../Services/RunningPackageService.cs | 135 ++++++++++++++++++ .../PackageManager/PackageCardViewModel.cs | 54 ++++--- .../PackageInstallDetailViewModel.cs | 2 +- .../ViewModels/RunningPackageViewModel.cs | 13 ++ .../Views/ConsoleOutputPage.axaml | 22 +++ .../Views/ConsoleOutputPage.axaml.cs | 13 ++ .../Helper/Factory/IPackageFactory.cs | 1 + .../Helper/Factory/PackageFactory.cs | 33 ++++- 8 files changed, 241 insertions(+), 32 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Services/RunningPackageService.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs create mode 100644 StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml create mode 100644 StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs new file mode 100644 index 00000000..d112a71f --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Controls.Notifications; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.Extensions.Logging; +using StabilityMatrix.Avalonia.Models; +using StabilityMatrix.Avalonia.ViewModels; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Extensions; +using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.Factory; +using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Packages; +using StabilityMatrix.Core.Python; +using StabilityMatrix.Core.Services; + +namespace StabilityMatrix.Avalonia.Services; + +[Singleton] +public partial class RunningPackageService( + ILogger logger, + IPackageFactory packageFactory, + INotificationService notificationService, + ISettingsManager settingsManager, + IPyRunner pyRunner +) : ObservableObject +{ + [ObservableProperty] + private ObservableDictionary runningPackages = []; + + public async Task StartPackage(InstalledPackage installedPackage, string? command = null) + { + var activeInstallName = installedPackage.PackageName; + var basePackage = string.IsNullOrWhiteSpace(activeInstallName) + ? null + : packageFactory.GetNewBasePackage(installedPackage); + + if (basePackage == null) + { + logger.LogWarning( + "During launch, package name '{PackageName}' did not match a definition", + activeInstallName + ); + + notificationService.Show( + new Notification( + "Package name invalid", + "Install package name did not match a definition. Please reinstall and let us know about this issue.", + NotificationType.Error + ) + ); + return null; + } + + // If this is the first launch (LaunchArgs is null), + // load and save a launch options dialog vm + // so that dynamic initial values are saved. + if (installedPackage.LaunchArgs == null) + { + var definitions = basePackage.LaunchOptions; + // Create config cards and save them + var cards = LaunchOptionCard + .FromDefinitions(definitions, Array.Empty()) + .ToImmutableArray(); + + var args = cards.SelectMany(c => c.Options).ToList(); + + logger.LogDebug( + "Setting initial launch args: {Args}", + string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr())) + ); + + settingsManager.SaveLaunchArgs(installedPackage.Id, args); + } + + if (basePackage is not StableSwarm) + { + await pyRunner.Initialize(); + } + + // Get path from package + var packagePath = new DirectoryPath(settingsManager.LibraryDir, installedPackage.LibraryPath!); + + if (basePackage is not StableSwarm) + { + // Unpack sitecustomize.py to venv + await UnpackSiteCustomize(packagePath.JoinDir("venv")); + } + + // Clear console and start update processing + var console = new ConsoleViewModel(); + console.StartUpdates(); + + // Update shared folder links (in case library paths changed) + await basePackage.UpdateModelFolders( + packagePath, + installedPackage.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod + ); + + // Load user launch args from settings and convert to string + var userArgs = installedPackage.LaunchArgs ?? []; + var userArgsString = string.Join(" ", userArgs.Select(opt => opt.ToArgString())); + + // Join with extras, if any + userArgsString = string.Join(" ", userArgsString, basePackage.ExtraLaunchArguments); + + // Use input command if provided, otherwise use package launch command + command ??= basePackage.LaunchCommand; + + await basePackage.RunPackage(packagePath, command, userArgsString, o => console.Post(o)); + var runningPackage = new PackagePair(installedPackage, basePackage); + + EventManager.Instance.OnRunningPackageStatusChanged(runningPackage); + + var viewModel = new RunningPackageViewModel(runningPackage, console); + RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel); + + return runningPackage.InstalledPackage.Id; + } + + public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) => + RunningPackages.TryGetValue(id, out var vm) ? vm : null; + + private static async Task UnpackSiteCustomize(DirectoryPath venvPath) + { + var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath); + var file = sitePackages.JoinFile("sitecustomize.py"); + file.Directory?.Create(); + await Assets.PyScriptSiteCustomize.ExtractTo(file, true); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index ba815fbf..20f97b72 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -26,7 +26,6 @@ using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.Packages; -using StabilityMatrix.Core.Models.Settings; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Services; @@ -34,15 +33,16 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager; [ManagedService] [Transient] -public partial class PackageCardViewModel : ProgressViewModel +public partial class PackageCardViewModel( + ILogger logger, + IPackageFactory packageFactory, + INotificationService notificationService, + ISettingsManager settingsManager, + INavigationService navigationService, + ServiceManager vmFactory, + RunningPackageService runningPackageService +) : ProgressViewModel { - private readonly ILogger logger; - private readonly IPackageFactory packageFactory; - private readonly INotificationService notificationService; - private readonly ISettingsManager settingsManager; - private readonly INavigationService navigationService; - private readonly ServiceManager vmFactory; - [ObservableProperty] private InstalledPackage? package; @@ -82,23 +82,6 @@ public partial class PackageCardViewModel : ProgressViewModel [ObservableProperty] private bool canUseExtensions; - public PackageCardViewModel( - ILogger logger, - IPackageFactory packageFactory, - INotificationService notificationService, - ISettingsManager settingsManager, - INavigationService navigationService, - ServiceManager vmFactory - ) - { - this.logger = logger; - this.packageFactory = packageFactory; - this.notificationService = notificationService; - this.settingsManager = settingsManager; - this.navigationService = navigationService; - this.vmFactory = vmFactory; - } - partial void OnPackageChanged(InstalledPackage? value) { if (string.IsNullOrWhiteSpace(value?.PackageName)) @@ -163,15 +146,26 @@ public partial class PackageCardViewModel : ProgressViewModel } } - public void Launch() + public async Task Launch() { if (Package == null) return; - settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id); + var packageId = await runningPackageService.StartPackage(Package); + + if (packageId != null) + { + var vm = runningPackageService.GetRunningPackageViewModel(packageId.Value); + if (vm != null) + { + navigationService.NavigateTo(vm, new BetterDrillInNavigationTransition()); + } + } - navigationService.NavigateTo(new BetterDrillInNavigationTransition()); - EventManager.Instance.OnPackageLaunchRequested(Package.Id); + // settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id); + // + // navigationService.NavigateTo(new BetterDrillInNavigationTransition()); + // EventManager.Instance.OnPackageLaunchRequested(Package.Id); } public async Task Uninstall() diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs index 71e14b31..9172d5c5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs @@ -281,7 +281,7 @@ public partial class PackageInstallDetailViewModel( SelectedVersion = !IsReleaseMode ? AvailableVersions?.FirstOrDefault(x => x.TagName == SelectedPackage.MainBranch) ?? AvailableVersions?.FirstOrDefault() - : AvailableVersions?.FirstOrDefault(); + : AvailableVersions?.FirstOrDefault(v => !v.IsPrerelease); CanInstall = !ShowDuplicateWarning; } diff --git a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs new file mode 100644 index 00000000..d2beffe6 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Avalonia.Views; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models; + +namespace StabilityMatrix.Avalonia.ViewModels; + +[View(typeof(ConsoleOutputPage))] +public class RunningPackageViewModel(PackagePair runningPackage, ConsoleViewModel console) : ViewModelBase +{ + public PackagePair RunningPackage { get; } = runningPackage; + public ConsoleViewModel Console { get; } = console; +} diff --git a/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml new file mode 100644 index 00000000..8d8bdbc2 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml @@ -0,0 +1,22 @@ + + + diff --git a/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs new file mode 100644 index 00000000..1c7fdae6 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views; + +[Transient] +public partial class ConsoleOutputPage : UserControlBase +{ + public ConsoleOutputPage() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs index b088c3bf..ccd855f8 100644 --- a/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs +++ b/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs @@ -10,4 +10,5 @@ public interface IPackageFactory BasePackage? this[string packageName] { get; } PackagePair? GetPackagePair(InstalledPackage? installedPackage); IEnumerable GetPackagesByType(PackageType packageType); + BasePackage GetNewBasePackage(InstalledPackage installedPackage); } diff --git a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs index a31bca2b..0e959e89 100644 --- a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs +++ b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs @@ -1,22 +1,53 @@ using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Packages; +using StabilityMatrix.Core.Services; namespace StabilityMatrix.Core.Helper.Factory; [Singleton(typeof(IPackageFactory))] public class PackageFactory : IPackageFactory { + private readonly IGithubApiCache githubApiCache; + private readonly ISettingsManager settingsManager; + private readonly IDownloadService downloadService; + private readonly IPrerequisiteHelper prerequisiteHelper; + /// /// Mapping of package.Name to package /// private readonly Dictionary basePackages; - public PackageFactory(IEnumerable basePackages) + public PackageFactory( + IEnumerable basePackages, + IGithubApiCache githubApiCache, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) { + this.githubApiCache = githubApiCache; + this.settingsManager = settingsManager; + this.downloadService = downloadService; + this.prerequisiteHelper = prerequisiteHelper; this.basePackages = basePackages.ToDictionary(x => x.Name); } + public BasePackage GetNewBasePackage(InstalledPackage installedPackage) + { + return installedPackage.PackageName switch + { + "ComfyUI" => new ComfyUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "Fooocus" => new Fooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "stable-diffusion-webui" + => new A3WebUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "Fooocus-ControlNet-SDXL" + => new FocusControlNet(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + _ => throw new ArgumentOutOfRangeException() + }; + } + public IEnumerable GetAllAvailablePackages() { return basePackages.Values.OrderBy(p => p.InstallerSortOrder).ThenBy(p => p.DisplayName); From fb8f06d6117f1e1f1a205670348a3ca51857c85d Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 26 Feb 2024 02:34:18 -0800 Subject: [PATCH 02/94] nav headers! and buttons! --- .../DesignData/DesignData.cs | 13 +++ .../Services/RunningPackageService.cs | 4 +- .../Styles/ThemeColors.axaml | 1 + .../ViewModels/NewPackageManagerViewModel.cs | 4 + .../PackageManager/PackageCardViewModel.cs | 101 +++++++++++++++++- .../ViewModels/RunningPackageViewModel.cs | 9 +- .../Views/PackageManagerPage.axaml | 80 +++++++++----- 7 files changed, 177 insertions(+), 35 deletions(-) diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 77d5a384..ee43cb1d 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -98,6 +98,19 @@ public static class DesignData }, LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui", LastUpdateCheck = DateTimeOffset.Now + }, + new() + { + Id = Guid.NewGuid(), + DisplayName = "Running Comfy", + PackageName = "ComfyUI", + Version = new InstalledPackageVersion + { + InstalledBranch = "master", + InstalledCommitSha = "abc12uwu345568972abaedf7g7e679a98879e879f87ga8" + }, + LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui", + LastUpdateCheck = DateTimeOffset.Now } }, ActiveInstalledPackageId = activePackageId diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs index d112a71f..44f6bf56 100644 --- a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs +++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs @@ -32,7 +32,7 @@ public partial class RunningPackageService( [ObservableProperty] private ObservableDictionary runningPackages = []; - public async Task StartPackage(InstalledPackage installedPackage, string? command = null) + public async Task StartPackage(InstalledPackage installedPackage, string? command = null) { var activeInstallName = installedPackage.PackageName; var basePackage = string.IsNullOrWhiteSpace(activeInstallName) @@ -119,7 +119,7 @@ public partial class RunningPackageService( var viewModel = new RunningPackageViewModel(runningPackage, console); RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel); - return runningPackage.InstalledPackage.Id; + return runningPackage; } public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) => diff --git a/StabilityMatrix.Avalonia/Styles/ThemeColors.axaml b/StabilityMatrix.Avalonia/Styles/ThemeColors.axaml index dbca9287..963fb0f7 100644 --- a/StabilityMatrix.Avalonia/Styles/ThemeColors.axaml +++ b/StabilityMatrix.Avalonia/Styles/ThemeColors.axaml @@ -21,6 +21,7 @@ #00BCD4 #009688 #2C582C + #2C582C #3A783C #4BA04F #AA4BA04F diff --git a/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs index d900f2bc..2170f6bf 100644 --- a/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs @@ -58,6 +58,10 @@ public partial class NewPackageManagerViewModel : PageViewModelBase { CurrentPagePath.Add(value); } + else if (value is RunningPackageViewModel) + { + CurrentPagePath.Add(value); + } else { CurrentPagePath.Clear(); diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index 20f97b72..45cd863d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; +using AsyncAwaitBestPractices; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Notifications; using Avalonia.Controls.Primitives; +using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Controls; @@ -38,11 +41,13 @@ public partial class PackageCardViewModel( IPackageFactory packageFactory, INotificationService notificationService, ISettingsManager settingsManager, - INavigationService navigationService, + INavigationService navigationService, ServiceManager vmFactory, RunningPackageService runningPackageService ) : ProgressViewModel { + private string webUiUrl = string.Empty; + [ObservableProperty] private InstalledPackage? package; @@ -82,6 +87,12 @@ public partial class PackageCardViewModel( [ObservableProperty] private bool canUseExtensions; + [ObservableProperty] + private bool isRunning; + + [ObservableProperty] + private bool showWebUiButton; + partial void OnPackageChanged(InstalledPackage? value) { if (string.IsNullOrWhiteSpace(value?.PackageName)) @@ -115,6 +126,12 @@ public partial class PackageCardViewModel( public override async Task OnLoadedAsync() { + if (Design.IsDesignMode && Package?.DisplayName == "Running Comfy") + { + IsRunning = true; + ShowWebUiButton = true; + } + if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet || Package is not { } currentPackage) return; @@ -151,14 +168,19 @@ public partial class PackageCardViewModel( if (Package == null) return; - var packageId = await runningPackageService.StartPackage(Package); + var packagePair = await runningPackageService.StartPackage(Package); - if (packageId != null) + if (packagePair != null) { - var vm = runningPackageService.GetRunningPackageViewModel(packageId.Value); + IsRunning = true; + + packagePair.BasePackage.Exited += BasePackageOnExited; + packagePair.BasePackage.StartupComplete += RunningPackageOnStartupComplete; + + var vm = runningPackageService.GetRunningPackageViewModel(packagePair.InstalledPackage.Id); if (vm != null) { - navigationService.NavigateTo(vm, new BetterDrillInNavigationTransition()); + navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition()); } } @@ -168,6 +190,69 @@ public partial class PackageCardViewModel( // EventManager.Instance.OnPackageLaunchRequested(Package.Id); } + public void NavToConsole() + { + if (Package == null) + return; + + var vm = runningPackageService.GetRunningPackageViewModel(Package.Id); + if (vm != null) + { + navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition()); + } + } + + public void LaunchWebUi() + { + if (string.IsNullOrEmpty(webUiUrl)) + return; + + notificationService.TryAsync( + Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)), + "Failed to open URL", + $"{webUiUrl}" + ); + } + + private void BasePackageOnExited(object? sender, int exitCode) + { + EventManager.Instance.OnRunningPackageStatusChanged(null); + Dispatcher + .UIThread.InvokeAsync(async () => + { + logger.LogTrace("Process exited ({Code}) at {Time:g}", exitCode, DateTimeOffset.Now); + + // Need to wait for streams to finish before detaching handlers + if (sender is BaseGitPackage { VenvRunner: not null } package) + { + var process = package.VenvRunner.Process; + if (process is not null) + { + // Max 5 seconds + var ct = new CancellationTokenSource(5000).Token; + try + { + await process.WaitUntilOutputEOF(ct); + } + catch (OperationCanceledException e) + { + logger.LogWarning("Waiting for process EOF timed out: {Message}", e.Message); + } + } + } + + // Detach handlers + if (sender is BasePackage basePackage) + { + basePackage.Exited -= BasePackageOnExited; + basePackage.StartupComplete -= RunningPackageOnStartupComplete; + } + + IsRunning = false; + }) + .SafeFireAndForget(); + } + public async Task Uninstall() { if (Package?.LibraryPath == null) @@ -559,4 +644,10 @@ public partial class PackageCardViewModel( IsSharedModelConfig = false; } } + + private void RunningPackageOnStartupComplete(object? sender, string e) + { + webUiUrl = e.Replace("0.0.0.0", "127.0.0.1"); + ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl); + } } diff --git a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs index d2beffe6..6b928e8a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs @@ -1,13 +1,18 @@ -using StabilityMatrix.Avalonia.ViewModels.Base; +using FluentAvalonia.UI.Controls; +using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Models; +using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource; namespace StabilityMatrix.Avalonia.ViewModels; [View(typeof(ConsoleOutputPage))] -public class RunningPackageViewModel(PackagePair runningPackage, ConsoleViewModel console) : ViewModelBase +public class RunningPackageViewModel(PackagePair runningPackage, ConsoleViewModel console) : PageViewModelBase { public PackagePair RunningPackage { get; } = runningPackage; public ConsoleViewModel Console { get; } = console; + + public override string Title => RunningPackage.InstalledPackage.PackageName ?? "Running Package"; + public override IconSource IconSource => new SymbolIconSource(); } diff --git a/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml b/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml index 456c3697..9416415d 100644 --- a/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml +++ b/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml @@ -10,11 +10,17 @@ xmlns:icons="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" + xmlns:avalonia="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="viewModels:PackageManagerViewModel" x:CompileBindings="True" d:DataContext="{x:Static designData:DesignData.PackageManagerViewModel}" x:Class="StabilityMatrix.Avalonia.Views.PackageManagerPage"> + + + + @@ -27,6 +33,13 @@ + + + + + + + @@ -221,13 +234,19 @@ Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" - IsVisible="{Binding IsUpdateAvailable}" - ColumnDefinitions="*, *"> + IsVisible="{Binding !IsUnknownPackage}" + ColumnDefinitions="*,Auto"> + + - - - + + ? DevModeSettingChanged; public event EventHandler? UpdateAvailable; public event EventHandler? PackageLaunchRequested; + public event EventHandler? PackageRelaunchRequested; public event EventHandler? ScrollToBottomRequested; public event EventHandler? ProgressChanged; public event EventHandler? RunningPackageStatusChanged; @@ -100,4 +101,7 @@ public class EventManager public void OnRecommendedModelsDialogClosed() => RecommendedModelsDialogClosed?.Invoke(this, EventArgs.Empty); + + public void OnPackageRelaunchRequested(InstalledPackage package) => + PackageRelaunchRequested?.Invoke(this, package); } From 45f48ce36f6d51835a1abbb000a258df73010a2b Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 27 Feb 2024 23:59:06 -0800 Subject: [PATCH 04/94] fix inference launch connection stuff to work with multi-package --- .../Services/RunningPackageService.cs | 4 - .../InferenceConnectionHelpViewModel.cs | 15 +-- .../ViewModels/InferenceViewModel.cs | 112 ++++++++++++------ .../PackageManager/PackageCardViewModel.cs | 65 ++++++++-- .../ViewModels/RunningPackageViewModel.cs | 19 ++- .../Helper/Factory/PackageFactory.cs | 45 ++++++- 6 files changed, 195 insertions(+), 65 deletions(-) diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs index 89f0d903..d3cb114f 100644 --- a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs +++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Immutable; -using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls.Notifications; @@ -10,7 +9,6 @@ using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; -using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Factory; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.FileInterfaces; @@ -114,8 +112,6 @@ public partial class RunningPackageService( await basePackage.RunPackage(packagePath, command, userArgsString, o => console.Post(o)); var runningPackage = new PackagePair(installedPackage, basePackage); - EventManager.Instance.OnRunningPackageStatusChanged(runningPackage); - var viewModel = new RunningPackageViewModel( settingsManager, notificationService, diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs index fafd1206..4f271517 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading.Tasks; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -30,6 +31,7 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa private readonly ISettingsManager settingsManager; private readonly INavigationService navigationService; private readonly IPackageFactory packageFactory; + private readonly RunningPackageService runningPackageService; [ObservableProperty] private string title = "Hello"; @@ -58,12 +60,14 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa public InferenceConnectionHelpViewModel( ISettingsManager settingsManager, INavigationService navigationService, - IPackageFactory packageFactory + IPackageFactory packageFactory, + RunningPackageService runningPackageService ) { this.settingsManager = settingsManager; this.navigationService = navigationService; this.packageFactory = packageFactory; + this.runningPackageService = runningPackageService; // Get comfy type installed packages var comfyPackages = this.settingsManager.Settings.InstalledPackages.Where( @@ -122,14 +126,11 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa /// Request launch of the selected package /// [RelayCommand] - private void LaunchSelectedPackage() + private async Task LaunchSelectedPackage() { - if (SelectedPackage?.Id is { } id) + if (SelectedPackage is not null) { - Dispatcher.UIThread.Post(() => - { - EventManager.Instance.OnPackageLaunchRequested(id); - }); + await runningPackageService.StartPackage(SelectedPackage); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs index c36fa128..42378ce2 100644 --- a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Linq; using System.Reactive.Linq; using System.Text.Json; @@ -51,6 +53,8 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable private readonly ServiceManager vmFactory; private readonly IModelIndexService modelIndexService; private readonly ILiteDbContext liteDbContext; + private readonly RunningPackageService runningPackageService; + private Guid? selectedPackageId; public override string Title => "Inference"; public override IconSource IconSource => @@ -86,6 +90,8 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable public bool IsComfyRunning => RunningPackage?.BasePackage is ComfyUI; + private IDisposable? onStartupComplete; + public InferenceViewModel( ServiceManager vmFactory, INotificationService notificationService, @@ -93,6 +99,7 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable ISettingsManager settingsManager, IModelIndexService modelIndexService, ILiteDbContext liteDbContext, + RunningPackageService runningPackageService, SharedState sharedState ) { @@ -101,12 +108,13 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable this.settingsManager = settingsManager; this.modelIndexService = modelIndexService; this.liteDbContext = liteDbContext; + this.runningPackageService = runningPackageService; ClientManager = inferenceClientManager; SharedState = sharedState; // Keep RunningPackage updated with the current package pair - EventManager.Instance.RunningPackageStatusChanged += OnRunningPackageStatusChanged; + runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged; // "Send to Inference" EventManager.Instance.InferenceTextToImageRequested += OnInferenceTextToImageRequested; @@ -118,54 +126,77 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable MenuOpenProjectCommand.WithConditionalNotificationErrorHandler(notificationService); } + private void DisconnectFromComfy() + { + RunningPackage = null; + + // Cancel any pending connection + if (ConnectCancelCommand.CanExecute(null)) + { + ConnectCancelCommand.Execute(null); + } + onStartupComplete?.Dispose(); + onStartupComplete = null; + IsWaitingForConnection = false; + + // Disconnect + Logger.Trace("On package close - disconnecting"); + DisconnectCommand.Execute(null); + } + /// /// Updates the RunningPackage property when the running package changes. /// Also starts a connection to the backend if a new ComfyUI package is running. /// And disconnects if the package is closed. /// - private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusChangedEventArgs e) + private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - RunningPackage = e.CurrentPackagePair; + if ( + e.NewItems?.OfType>().Select(x => x.Value) + is not { } newItems + ) + { + if (RunningPackage != null) + { + DisconnectFromComfy(); + } + return; + } - IDisposable? onStartupComplete = null; + var comfyViewModel = newItems.FirstOrDefault( + vm => + vm.RunningPackage.InstalledPackage.Id == selectedPackageId + || vm.RunningPackage.BasePackage is ComfyUI + ); - Dispatcher.UIThread.Post(() => + if (comfyViewModel is null && RunningPackage?.BasePackage is ComfyUI) { - if (e.CurrentPackagePair?.BasePackage is ComfyUI package) - { - IsWaitingForConnection = true; - onStartupComplete = Observable - .FromEventPattern(package, nameof(package.StartupComplete)) - .Take(1) - .Subscribe(_ => + DisconnectFromComfy(); + } + else if (comfyViewModel != null && RunningPackage == null) + { + IsWaitingForConnection = true; + RunningPackage = comfyViewModel.RunningPackage; + onStartupComplete = Observable + .FromEventPattern( + comfyViewModel.RunningPackage.BasePackage, + nameof(comfyViewModel.RunningPackage.BasePackage.StartupComplete) + ) + .Take(1) + .Subscribe(_ => + { + Dispatcher.UIThread.Post(() => { - Dispatcher.UIThread.Post(() => + if (ConnectCommand.CanExecute(null)) { - if (ConnectCommand.CanExecute(null)) - { - Logger.Trace("On package launch - starting connection"); - ConnectCommand.Execute(null); - } - IsWaitingForConnection = false; - }); - }); - } - else - { - // Cancel any pending connection - if (ConnectCancelCommand.CanExecute(null)) - { - ConnectCancelCommand.Execute(null); - } - onStartupComplete?.Dispose(); - onStartupComplete = null; - IsWaitingForConnection = false; + Logger.Trace("On package launch - starting connection"); + ConnectCommand.Execute(null); + } - // Disconnect - Logger.Trace("On package close - disconnecting"); - DisconnectCommand.Execute(null); - } - }); + IsWaitingForConnection = false; + }); + }); + } } public override void OnLoaded() @@ -390,7 +421,12 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable private async Task ShowConnectionHelp() { var vm = vmFactory.Get(); - await vm.CreateDialog().ShowAsync(); + var result = await vm.CreateDialog().ShowAsync(); + + if (result != ContentDialogResult.Primary) + return; + + selectedPackageId = vm.SelectedPackage?.Id; } /// diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index 2e8bf0a9..614fef81 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -36,15 +37,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager; [ManagedService] [Transient] -public partial class PackageCardViewModel( - ILogger logger, - IPackageFactory packageFactory, - INotificationService notificationService, - ISettingsManager settingsManager, - INavigationService navigationService, - ServiceManager vmFactory, - RunningPackageService runningPackageService -) : ProgressViewModel +public partial class PackageCardViewModel : ProgressViewModel { private string webUiUrl = string.Empty; @@ -93,6 +86,59 @@ public partial class PackageCardViewModel( [ObservableProperty] private bool showWebUiButton; + private readonly ILogger logger; + private readonly IPackageFactory packageFactory; + private readonly INotificationService notificationService; + private readonly ISettingsManager settingsManager; + private readonly INavigationService navigationService; + private readonly ServiceManager vmFactory; + private readonly RunningPackageService runningPackageService; + + /// + public PackageCardViewModel( + ILogger logger, + IPackageFactory packageFactory, + INotificationService notificationService, + ISettingsManager settingsManager, + INavigationService navigationService, + ServiceManager vmFactory, + RunningPackageService runningPackageService + ) + { + this.logger = logger; + this.packageFactory = packageFactory; + this.notificationService = notificationService; + this.settingsManager = settingsManager; + this.navigationService = navigationService; + this.vmFactory = vmFactory; + this.runningPackageService = runningPackageService; + + runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged; + } + + private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if ( + e.NewItems?.OfType>().Select(x => x.Value) + is not { } newItems + ) + return; + + var runningViewModel = newItems.FirstOrDefault( + x => x.RunningPackage.InstalledPackage.Id == Package?.Id + ); + if (runningViewModel is not null) + { + IsRunning = true; + runningViewModel.RunningPackage.BasePackage.Exited += BasePackageOnExited; + runningViewModel.RunningPackage.BasePackage.StartupComplete += RunningPackageOnStartupComplete; + } + else if (runningViewModel is null && IsRunning) + { + IsRunning = false; + } + } + partial void OnPackageChanged(InstalledPackage? value) { if (string.IsNullOrWhiteSpace(value?.PackageName)) @@ -232,7 +278,6 @@ public partial class PackageCardViewModel( private void BasePackageOnExited(object? sender, int exitCode) { - EventManager.Instance.OnRunningPackageStatusChanged(null); Dispatcher .UIThread.InvokeAsync(async () => { diff --git a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs index 2c188633..2758269f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs @@ -1,7 +1,5 @@ using System; using System.Threading.Tasks; -using Avalonia.Threading; -using AvaloniaEdit; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Controls; @@ -19,7 +17,7 @@ using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource; namespace StabilityMatrix.Avalonia.ViewModels; [View(typeof(ConsoleOutputPage))] -public partial class RunningPackageViewModel : PageViewModelBase +public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, IAsyncDisposable { private readonly INotificationService notificationService; private string webUiUrl = string.Empty; @@ -51,7 +49,7 @@ public partial class RunningPackageViewModel : PageViewModelBase Console = console; Console.Document.LineCountChanged += DocumentOnLineCountChanged; RunningPackage.BasePackage.StartupComplete += BasePackageOnStartupComplete; - runningPackage.BasePackage.Exited += BasePackageOnExited; + RunningPackage.BasePackage.Exited += BasePackageOnExited; settingsManager.RelayPropertyFor( this, @@ -65,6 +63,9 @@ public partial class RunningPackageViewModel : PageViewModelBase { IsRunning = false; ShowWebUiButton = false; + Console.Document.LineCountChanged -= DocumentOnLineCountChanged; + RunningPackage.BasePackage.StartupComplete -= BasePackageOnStartupComplete; + RunningPackage.BasePackage.Exited -= BasePackageOnExited; } private void BasePackageOnStartupComplete(object? sender, string url) @@ -123,4 +124,14 @@ public partial class RunningPackageViewModel : PageViewModelBase } } } + + public void Dispose() + { + Console.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await Console.DisposeAsync(); + } } diff --git a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs index 0e959e89..3e1ccee2 100644 --- a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs +++ b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs @@ -2,6 +2,7 @@ using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Packages; +using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Services; namespace StabilityMatrix.Core.Helper.Factory; @@ -13,6 +14,7 @@ public class PackageFactory : IPackageFactory private readonly ISettingsManager settingsManager; private readonly IDownloadService downloadService; private readonly IPrerequisiteHelper prerequisiteHelper; + private readonly IPyRunner pyRunner; /// /// Mapping of package.Name to package @@ -24,13 +26,15 @@ public class PackageFactory : IPackageFactory IGithubApiCache githubApiCache, ISettingsManager settingsManager, IDownloadService downloadService, - IPrerequisiteHelper prerequisiteHelper + IPrerequisiteHelper prerequisiteHelper, + IPyRunner pyRunner ) { this.githubApiCache = githubApiCache; this.settingsManager = settingsManager; this.downloadService = downloadService; this.prerequisiteHelper = prerequisiteHelper; + this.pyRunner = pyRunner; this.basePackages = basePackages.ToDictionary(x => x.Name); } @@ -44,7 +48,44 @@ public class PackageFactory : IPackageFactory => new A3WebUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper), "Fooocus-ControlNet-SDXL" => new FocusControlNet(githubApiCache, settingsManager, downloadService, prerequisiteHelper), - _ => throw new ArgumentOutOfRangeException() + "Fooocus-MRE" + => new FooocusMre(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "InvokeAI" => new InvokeAI(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "kohya_ss" + => new KohyaSs( + githubApiCache, + settingsManager, + downloadService, + prerequisiteHelper, + pyRunner + ), + "OneTrainer" + => new OneTrainer(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "RuinedFooocus" + => new RuinedFooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "stable-diffusion-webui-forge" + => new SDWebForge(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "stable-diffusion-webui-directml" + => new StableDiffusionDirectMl( + githubApiCache, + settingsManager, + downloadService, + prerequisiteHelper + ), + "stable-diffusion-webui-ux" + => new StableDiffusionUx( + githubApiCache, + settingsManager, + downloadService, + prerequisiteHelper + ), + "StableSwarmUI" + => new StableSwarm(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "automatic" + => new VladAutomatic(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "voltaML-fast-stable-diffusion" + => new VoltaML(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + _ => throw new ArgumentOutOfRangeException(nameof(installedPackage)) }; } From 91fb39f3669e642c965bc4006ca6aea215f1426d Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 28 Feb 2024 00:20:57 -0800 Subject: [PATCH 05/94] fix some process exit stuff --- .../Services/RunningPackageService.cs | 2 + .../PackageManager/PackageCardViewModel.cs | 60 +++++++++---------- .../ViewModels/RunningPackageViewModel.cs | 21 ++++--- 3 files changed, 43 insertions(+), 40 deletions(-) diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs index d3cb114f..96f1d719 100644 --- a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs +++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs @@ -27,6 +27,7 @@ public partial class RunningPackageService( IPyRunner pyRunner ) : ObservableObject { + // 🤔 what if we put the ConsoleViewModel inside the BasePackage? 🤔 [ObservableProperty] private ObservableDictionary runningPackages = []; @@ -115,6 +116,7 @@ public partial class RunningPackageService( var viewModel = new RunningPackageViewModel( settingsManager, notificationService, + this, runningPackage, console ); diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index 614fef81..8c68505c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -37,7 +37,15 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager; [ManagedService] [Transient] -public partial class PackageCardViewModel : ProgressViewModel +public partial class PackageCardViewModel( + ILogger logger, + IPackageFactory packageFactory, + INotificationService notificationService, + ISettingsManager settingsManager, + INavigationService navigationService, + ServiceManager vmFactory, + RunningPackageService runningPackageService +) : ProgressViewModel { private string webUiUrl = string.Empty; @@ -86,36 +94,6 @@ public partial class PackageCardViewModel : ProgressViewModel [ObservableProperty] private bool showWebUiButton; - private readonly ILogger logger; - private readonly IPackageFactory packageFactory; - private readonly INotificationService notificationService; - private readonly ISettingsManager settingsManager; - private readonly INavigationService navigationService; - private readonly ServiceManager vmFactory; - private readonly RunningPackageService runningPackageService; - - /// - public PackageCardViewModel( - ILogger logger, - IPackageFactory packageFactory, - INotificationService notificationService, - ISettingsManager settingsManager, - INavigationService navigationService, - ServiceManager vmFactory, - RunningPackageService runningPackageService - ) - { - this.logger = logger; - this.packageFactory = packageFactory; - this.notificationService = notificationService; - this.settingsManager = settingsManager; - this.navigationService = navigationService; - this.vmFactory = vmFactory; - this.runningPackageService = runningPackageService; - - runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged; - } - private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if ( @@ -167,6 +145,9 @@ public partial class PackageCardViewModel : ProgressViewModel UseSharedOutput = Package?.UseSharedOutputFolder ?? false; CanUseSharedOutput = basePackage?.SharedOutputFolders != null; CanUseExtensions = basePackage?.SupportsExtensions ?? false; + + runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged; + EventManager.Instance.PackageRelaunchRequested += InstanceOnPackageRelaunchRequested; } } @@ -190,8 +171,6 @@ public partial class PackageCardViewModel : ProgressViewModel if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet || Package is not { } currentPackage) return; - EventManager.Instance.PackageRelaunchRequested += InstanceOnPackageRelaunchRequested; - if ( packageFactory.FindPackageByName(currentPackage.PackageName) is { } basePackage @@ -217,12 +196,27 @@ public partial class PackageCardViewModel : ProgressViewModel } IsUpdateAvailable = await HasUpdate(); + + if ( + Package != null + && !IsRunning + && runningPackageService.RunningPackages.TryGetValue(Package.Id, out var runningPackageVm) + ) + { + IsRunning = true; + runningPackageVm.RunningPackage.BasePackage.Exited += BasePackageOnExited; + runningPackageVm.RunningPackage.BasePackage.StartupComplete += + RunningPackageOnStartupComplete; + webUiUrl = runningPackageVm.WebUiUrl; + ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl); + } } } public override void OnUnloaded() { EventManager.Instance.PackageRelaunchRequested -= InstanceOnPackageRelaunchRequested; + runningPackageService.RunningPackages.CollectionChanged -= RunningPackagesOnCollectionChanged; } public async Task Launch() diff --git a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs index 2758269f..3b879d95 100644 --- a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs @@ -20,7 +20,7 @@ namespace StabilityMatrix.Avalonia.ViewModels; public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, IAsyncDisposable { private readonly INotificationService notificationService; - private string webUiUrl = string.Empty; + private readonly RunningPackageService runningPackageService; public PackagePair RunningPackage { get; } public ConsoleViewModel Console { get; } @@ -33,6 +33,9 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I [ObservableProperty] private bool showWebUiButton; + [ObservableProperty] + private string webUiUrl = string.Empty; + [ObservableProperty] private bool isRunning = true; @@ -40,11 +43,14 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I public RunningPackageViewModel( ISettingsManager settingsManager, INotificationService notificationService, + RunningPackageService runningPackageService, PackagePair runningPackage, ConsoleViewModel console ) { this.notificationService = notificationService; + this.runningPackageService = runningPackageService; + RunningPackage = runningPackage; Console = console; Console.Document.LineCountChanged += DocumentOnLineCountChanged; @@ -66,12 +72,13 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I Console.Document.LineCountChanged -= DocumentOnLineCountChanged; RunningPackage.BasePackage.StartupComplete -= BasePackageOnStartupComplete; RunningPackage.BasePackage.Exited -= BasePackageOnExited; + runningPackageService.RunningPackages.Remove(RunningPackage.InstalledPackage.Id); } private void BasePackageOnStartupComplete(object? sender, string url) { - webUiUrl = url.Replace("0.0.0.0", "127.0.0.1"); - ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl); + WebUiUrl = url.Replace("0.0.0.0", "127.0.0.1"); + ShowWebUiButton = !string.IsNullOrWhiteSpace(WebUiUrl); } private void DocumentOnLineCountChanged(object? sender, EventArgs e) @@ -91,7 +98,7 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I [RelayCommand] private async Task Stop() { - await RunningPackage.BasePackage.WaitForShutdown(); + await runningPackageService.StopPackage(RunningPackage.InstalledPackage.Id); Console.PostLine($"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}"); await Console.StopUpdatesAsync(); IsRunning = false; @@ -100,13 +107,13 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I [RelayCommand] private void LaunchWebUi() { - if (string.IsNullOrEmpty(webUiUrl)) + if (string.IsNullOrEmpty(WebUiUrl)) return; notificationService.TryAsync( - Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)), + Task.Run(() => ProcessRunner.OpenUrl(WebUiUrl)), "Failed to open URL", - $"{webUiUrl}" + $"{WebUiUrl}" ); } From 7e7b0148aa98671ae83ccc54a9c0c4455f5fd432 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 28 Feb 2024 00:22:08 -0800 Subject: [PATCH 06/94] "fix" tests --- .../Helper/PackageFactoryTests.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs b/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs index 4163922d..b4920bdb 100644 --- a/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs +++ b/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs @@ -8,31 +8,28 @@ public class PackageFactoryTests { private PackageFactory packageFactory = null!; private IEnumerable fakeBasePackages = null!; - + [TestInitialize] public void Setup() { - fakeBasePackages = new List - { - new DankDiffusion(null!, null!, null!, null!) - }; - packageFactory = new PackageFactory(fakeBasePackages); + fakeBasePackages = new List { new DankDiffusion(null!, null!, null!, null!) }; + packageFactory = new PackageFactory(fakeBasePackages, null, null, null, null, null); } - + [TestMethod] public void GetAllAvailablePackages_ReturnsAllPackages() { var result = packageFactory.GetAllAvailablePackages(); Assert.AreEqual(1, result.Count()); } - + [TestMethod] public void FindPackageByName_ReturnsPackage() { var result = packageFactory.FindPackageByName("dank-diffusion"); Assert.IsNotNull(result); } - + [TestMethod] public void FindPackageByName_ReturnsNull() { From bce305b216d8f5e162faccb4b3b402241f2623c2 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 29 Feb 2024 19:11:51 -0800 Subject: [PATCH 07/94] Fix civitai login dialog not showing when needed --- CHANGELOG.md | 1 + StabilityMatrix.Core/Services/DownloadService.cs | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f5de92..a12e0b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - (Internal) Updated to Avalonia 11.0.9 ### Fixed - Fixed image viewer dialog arrow key navigation not working +- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins ## v2.9.0-pre.1 ### Added diff --git a/StabilityMatrix.Core/Services/DownloadService.cs b/StabilityMatrix.Core/Services/DownloadService.cs index 1aeeb9d7..ffc44ebf 100644 --- a/StabilityMatrix.Core/Services/DownloadService.cs +++ b/StabilityMatrix.Core/Services/DownloadService.cs @@ -1,4 +1,5 @@ -using System.Net.Http.Headers; +using System.Net; +using System.Net.Http.Headers; using Microsoft.Extensions.Logging; using Polly.Contrib.WaitAndRetry; using StabilityMatrix.Core.Attributes; @@ -184,6 +185,10 @@ public class DownloadService : IDownloadService throw new UnauthorizedAccessException(); } } + else if (noRedirectResponse.StatusCode == HttpStatusCode.Unauthorized) + { + throw new UnauthorizedAccessException(); + } using var redirectRequest = new HttpRequestMessage(); redirectRequest.Method = HttpMethod.Get; From 477d5302dd2f271ebff0c4de7e612863175d6c7e Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 29 Feb 2024 19:24:18 -0800 Subject: [PATCH 08/94] moar options in all the model browsers --- CHANGELOG.md | 2 ++ StabilityMatrix.Avalonia/Assets/hf-packages.json | 11 +++++++++++ .../Models/Api/CivitBaseModelType.cs | 12 ++++++++++++ 3 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a12e0b83..752dd145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.9.0-pre.2 ### Added - Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`) +- Added more Base Model search options to the CivitAI Model Browser +- Added more models to the HuggingFace Model Browser ### Changed - (Internal) Updated to Avalonia 11.0.9 ### Fixed diff --git a/StabilityMatrix.Avalonia/Assets/hf-packages.json b/StabilityMatrix.Avalonia/Assets/hf-packages.json index c0a12099..20c89af6 100644 --- a/StabilityMatrix.Avalonia/Assets/hf-packages.json +++ b/StabilityMatrix.Avalonia/Assets/hf-packages.json @@ -58,6 +58,17 @@ "LicenseType": "Stable-Video-Diffusion-NC-Community", "LicensePath": "LICENSE" }, + { + "ModelCategory": "BaseModel", + "ModelName": "Stable Cascade", + "RepositoryPath": "stabilityai/stable-cascade", + "Files": [ + "comfyui_checkpoints/stable_cascade_stage_b.safetensors", + "comfyui_checkpoints/stable_cascade_stage_c.safetensors" + ], + "LicenseType": "stable-cascade-nc-community", + "LicensePath": "LICENSE" + }, { "ModelCategory": "ControlNet", "ModelName": "Canny", diff --git a/StabilityMatrix.Core/Models/Api/CivitBaseModelType.cs b/StabilityMatrix.Core/Models/Api/CivitBaseModelType.cs index dd886f77..9721d717 100644 --- a/StabilityMatrix.Core/Models/Api/CivitBaseModelType.cs +++ b/StabilityMatrix.Core/Models/Api/CivitBaseModelType.cs @@ -26,6 +26,18 @@ public enum CivitBaseModelType [StringValue("SDXL 1.0 LCM")] Sdxl10Lcm, + [StringValue("SDXL Distilled")] + SdxlDistilled, + + [StringValue("SDXL Lightning")] + SdxlLightning, + + [StringValue("SVD")] + SVD, + + [StringValue("Stable Cascade")] + StableCascade, + [StringValue("SDXL Turbo")] SdxlTurbo, Other, From f27671bc9d0102fac754775c1bb19165a3155419 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 29 Feb 2024 19:32:10 -0800 Subject: [PATCH 09/94] name --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 752dd145..8dcc2910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ### Added - Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`) - Added more Base Model search options to the CivitAI Model Browser -- Added more models to the HuggingFace Model Browser +- Added Stable Cascade to the HuggingFace Model Browser ### Changed - (Internal) Updated to Avalonia 11.0.9 ### Fixed From e1816f71c6e09491e36383456c6c53e9ba1fdf35 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 29 Feb 2024 23:38:32 -0500 Subject: [PATCH 10/94] Add GetOptionalNodeOptionNames apis --- StabilityMatrix.Core/Inference/ComfyClient.cs | 15 +++++++++++++++ .../Models/Api/Comfy/ComfyInputInfo.cs | 17 ++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/StabilityMatrix.Core/Inference/ComfyClient.cs b/StabilityMatrix.Core/Inference/ComfyClient.cs index 72ae9a27..9e7ab6bf 100644 --- a/StabilityMatrix.Core/Inference/ComfyClient.cs +++ b/StabilityMatrix.Core/Inference/ComfyClient.cs @@ -460,6 +460,21 @@ public class ComfyClient : InferenceClientBase return info.Input.GetRequiredValueAsNestedList(optionName); } + /// + /// Get a list of strings representing available optional options of a given node + /// + public async Task?> GetOptionalNodeOptionNamesAsync( + string nodeName, + string optionName, + CancellationToken cancellationToken = default + ) + { + var response = await comfyApi.GetObjectInfo(nodeName, cancellationToken).ConfigureAwait(false); + + var info = response[nodeName]; + return info.Input.GetOptionalValueAsNestedList(optionName); + } + protected override void Dispose(bool disposing) { if (isDisposed) diff --git a/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs b/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs index ecd04374..60072ac4 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs @@ -9,13 +9,24 @@ public class ComfyInputInfo [JsonPropertyName("required")] public Dictionary? Required { get; set; } + [JsonPropertyName("optional")] + public Dictionary? Optional { get; set; } + public List? GetRequiredValueAsNestedList(string key) { var value = Required?[key]; - if (value is null) return null; - var nested = value.Deserialize>>(); - + var nested = value?.Deserialize>>(); + return nested?.SelectMany(x => x).ToList(); } + + public List? GetOptionalValueAsNestedList(string key) + { + var value = Optional?[key]; + + var nested = value?.Deserialize()?[0].Deserialize>(); + + return nested; + } } From a0221c7d91da9c21192323240866c2859e957966 Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 29 Feb 2024 23:38:50 -0500 Subject: [PATCH 11/94] Add Inference_Core_AIO_Preprocessor support --- .../Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index 286dff0b..f408dd96 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -342,6 +342,20 @@ public class ComfyNodeBuilder public bool LogPrompt { get; init; } } + [TypedNodeOptions( + Name = "Inference_Core_AIO_Preprocessor", + RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes"] + )] + public record AIOPreprocessor : ComfyTypedNodeBase + { + public required ImageNodeConnection Image { get; init; } + + public required string Preprocessor { get; init; } + + [Range(64, 2048)] + public int Resolution { get; init; } = 512; + } + public ImageNodeConnection Lambda_LatentToImage(LatentNodeConnection latent, VAENodeConnection vae) { var name = GetUniqueName("VAEDecode"); From 4cccde8a4b846b81f3669fef78352b4b5ed87a9e Mon Sep 17 00:00:00 2001 From: Ionite Date: Thu, 29 Feb 2024 23:39:18 -0500 Subject: [PATCH 12/94] Implement preprocessor support for ControlNet module --- .../Controls/Inference/ControlNetCard.axaml | 10 ++++----- .../DesignData/MockInferenceClientManager.cs | 3 +++ .../Services/IInferenceClientManager.cs | 1 + .../Services/InferenceClientManager.cs | 19 ++++++++++++++++ .../Inference/ControlNetCardViewModel.cs | 7 +----- .../Inference/Modules/ControlNetModule.cs | 22 +++++++++++++++---- 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml index b43ea05b..cb3b8567 100644 --- a/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml @@ -47,18 +47,16 @@ DataContext="{Binding SelectImageCardViewModel}" FontSize="13" /> - + + Theme="{StaticResource FAComboBoxHybridModelTheme}"/> Schedulers { get; } = new ObservableCollectionExtended(ComfyScheduler.Defaults); + public IObservableCollection Preprocessors { get; } = + new ObservableCollectionExtended(); + [ObservableProperty] [NotifyPropertyChangedFor(nameof(CanUserConnect))] private bool isConnected; diff --git a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs index fc134c74..c4b990b3 100644 --- a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs +++ b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs @@ -44,6 +44,7 @@ public interface IInferenceClientManager : IDisposable, INotifyPropertyChanged, IObservableCollection Samplers { get; } IObservableCollection Upscalers { get; } IObservableCollection Schedulers { get; } + IObservableCollection Preprocessors { get; } Task CopyImageToInputAsync(FilePath imageFile, CancellationToken cancellationToken = default); diff --git a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs index 5ad092af..892031bf 100644 --- a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs +++ b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs @@ -101,6 +101,11 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient public IObservableCollection Schedulers { get; } = new ObservableCollectionExtended(); + public IObservableCollection Preprocessors { get; } = + new ObservableCollectionExtended(); + + private readonly SourceCache preprocessorsSource = new(p => p.GetId()); + public InferenceClientManager( ILogger logger, IApiFactory apiFactory, @@ -166,6 +171,8 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient schedulersSource.Connect().DeferUntilLoaded().Bind(Schedulers).Subscribe(); + preprocessorsSource.Connect().DeferUntilLoaded().Bind(Preprocessors).Subscribe(); + settingsManager.RegisterOnLibraryDirSet(_ => { Dispatcher.UIThread.Post(ResetSharedProperties, DispatcherPriority.Background); @@ -270,6 +277,18 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient }); logger.LogTrace("Loaded scheduler methods: {@Schedulers}", schedulerNames); } + + // Add preprocessor names from Inference_Core_AIO_Preprocessor node (might not exist if no extension) + if ( + await Client.GetOptionalNodeOptionNamesAsync("Inference_Core_AIO_Preprocessor", "preprocessor") is + { } preprocessorNames + ) + { + preprocessorsSource.EditDiff( + preprocessorNames.Select(HybridModelFile.FromRemote), + HybridModelFile.Comparer + ); + } } /// diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs index 496e90fc..3cc9fd8d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs @@ -1,5 +1,4 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -9,11 +8,7 @@ using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Core.Attributes; -using StabilityMatrix.Core.Extensions; -using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models; -using StabilityMatrix.Core.Models.FileInterfaces; -using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.ViewModels.Inference; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs index 23260daf..f681f053 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs @@ -39,7 +39,7 @@ public class ControlNetModule : ModuleBase { var card = GetCard(); - var imageLoad = e.Nodes.AddTypedNode( + var image = e.Nodes.AddTypedNode( new ComfyNodeBuilder.LoadImage { Name = e.Nodes.GetUniqueName("ControlNet_LoadImage"), @@ -47,7 +47,21 @@ public class ControlNetModule : ModuleBase card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference") ?? throw new ValidationException("No ImageSource") } - ); + ).Output1; + + if (card.SelectedPreprocessor is { } preprocessor && preprocessor.RelativePath != "none") + { + var aioPreprocessor = e.Nodes.AddTypedNode( + new ComfyNodeBuilder.AIOPreprocessor + { + Name = e.Nodes.GetUniqueName("ControlNet_Preprocessor"), + Image = image, + Preprocessor = preprocessor.RelativePath + } + ); + + image = aioPreprocessor.Output; + } var controlNetLoader = e.Nodes.AddTypedNode( new ComfyNodeBuilder.ControlNetLoader @@ -62,7 +76,7 @@ public class ControlNetModule : ModuleBase new ComfyNodeBuilder.ControlNetApplyAdvanced { Name = e.Nodes.GetUniqueName("ControlNetApply"), - Image = imageLoad.Output1, + Image = image, ControlNet = controlNetLoader.Output, Positive = e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"), Negative = e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"), @@ -81,7 +95,7 @@ public class ControlNetModule : ModuleBase new ComfyNodeBuilder.ControlNetApplyAdvanced { Name = e.Nodes.GetUniqueName("Refiner_ControlNetApply"), - Image = imageLoad.Output1, + Image = image, ControlNet = controlNetLoader.Output, Positive = e.Temp.RefinerConditioning.Positive, Negative = e.Temp.RefinerConditioning.Negative, From deb86fef8345bd7c65c3fdd319913f64e2556c84 Mon Sep 17 00:00:00 2001 From: Ionite Date: Fri, 1 Mar 2024 16:42:21 -0500 Subject: [PATCH 13/94] Add ExtensionSpecifier parsing --- .../Attributes/TypedNodeOptionsAttribute.cs | 6 + .../Models/Api/Comfy/Nodes/NodeDictionary.cs | 13 ++- .../Packages/Extensions/ExtensionSpecifier.cs | 105 ++++++++++++++++++ 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs diff --git a/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs b/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs index f496abc4..44d019fd 100644 --- a/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs +++ b/StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs @@ -1,4 +1,5 @@ using StabilityMatrix.Core.Models.Api.Comfy.Nodes; +using StabilityMatrix.Core.Models.Packages.Extensions; namespace StabilityMatrix.Core.Attributes; @@ -11,4 +12,9 @@ public class TypedNodeOptionsAttribute : Attribute public string? Name { get; init; } public string[]? RequiredExtensions { get; init; } + + public IEnumerable GetRequiredExtensionSpecifiers() + { + return RequiredExtensions?.Select(ExtensionSpecifier.Parse) ?? Enumerable.Empty(); + } } diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs index 5fbe4464..da088767 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs @@ -1,10 +1,12 @@ using System.ComponentModel; using System.Reflection; using System.Text.Json.Serialization; +using KGySoft.CoreLibraries; using OneOf; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; +using StabilityMatrix.Core.Models.Packages.Extensions; namespace StabilityMatrix.Core.Models.Api.Comfy.Nodes; @@ -19,7 +21,10 @@ public class NodeDictionary : Dictionary /// When inserting TypedNodes, this holds a mapping of ClassType to required extensions /// [JsonIgnore] - public Dictionary ClassTypeRequiredExtensions { get; } = new(); + public Dictionary ClassTypeRequiredExtensions { get; } = new(); + + public IEnumerable RequiredExtensions => + ClassTypeRequiredExtensions.Values.SelectMany(x => x); /// /// Finds a unique node name given a base name, by appending _2, _3, etc. @@ -63,7 +68,11 @@ public class NodeDictionary : Dictionary { if (options.RequiredExtensions != null) { - ClassTypeRequiredExtensions[namedNode.ClassType] = options.RequiredExtensions; + ClassTypeRequiredExtensions.AddOrUpdate( + namedNode.ClassType, + _ => options.GetRequiredExtensionSpecifiers().ToArray(), + (_, specifiers) => options.GetRequiredExtensionSpecifiers().Concat(specifiers).ToArray() + ); } } diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs b/StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs new file mode 100644 index 00000000..13003ca0 --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs @@ -0,0 +1,105 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using Semver; +using StabilityMatrix.Core.Processes; + +namespace StabilityMatrix.Core.Models.Packages.Extensions; + +/// +/// Extension specifier with optional version constraints. +/// +[PublicAPI] +public partial class ExtensionSpecifier +{ + public required string Name { get; init; } + + public string? Constraint { get; init; } + + public string? Version { get; init; } + + public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Version; + + public bool TryGetSemVersionRange([NotNullWhen(true)] out SemVersionRange? semVersionRange) + { + if (!string.IsNullOrEmpty(VersionConstraint)) + { + return SemVersionRange.TryParse( + VersionConstraint, + SemVersionRangeOptions.Loose, + out semVersionRange + ); + } + + semVersionRange = null; + return false; + } + + public static ExtensionSpecifier Parse(string value) + { + TryParse(value, true, out var packageSpecifier); + + return packageSpecifier!; + } + + public static bool TryParse(string value, [NotNullWhen(true)] out ExtensionSpecifier? extensionSpecifier) + { + return TryParse(value, false, out extensionSpecifier); + } + + private static bool TryParse( + string value, + bool throwOnFailure, + [NotNullWhen(true)] out ExtensionSpecifier? packageSpecifier + ) + { + var match = ExtensionSpecifierRegex().Match(value); + if (!match.Success) + { + if (throwOnFailure) + { + throw new ArgumentException($"Invalid extension specifier: {value}"); + } + + packageSpecifier = null; + return false; + } + + packageSpecifier = new ExtensionSpecifier + { + Name = match.Groups["extension_name"].Value, + Constraint = match.Groups["version_constraint"].Value, + Version = match.Groups["version"].Value + }; + + return true; + } + + /// + public override string ToString() + { + return Name + VersionConstraint; + } + + public static implicit operator Argument(ExtensionSpecifier specifier) + { + return specifier.VersionConstraint is null + ? new Argument(specifier.Name) + : new Argument((specifier.Name, specifier.VersionConstraint)); + } + + public static implicit operator ExtensionSpecifier(string specifier) + { + return Parse(specifier); + } + + /// + /// Regex to match a pip package specifier. + /// + [GeneratedRegex( + @"(?\S+)\s*(?==|>=|<=|>|<|~=|!=)?\s*(?[a-zA-Z0-9_.]+)?", + RegexOptions.CultureInvariant, + 5000 + )] + private static partial Regex ExtensionSpecifierRegex(); +} From 3653fb475e38a17dd5162d86edbd963ae67e788b Mon Sep 17 00:00:00 2001 From: Ionite Date: Fri, 1 Mar 2024 16:43:27 -0500 Subject: [PATCH 14/94] Fix missing node causing connection error --- StabilityMatrix.Core/Inference/ComfyClient.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Core/Inference/ComfyClient.cs b/StabilityMatrix.Core/Inference/ComfyClient.cs index 9e7ab6bf..3f3b894e 100644 --- a/StabilityMatrix.Core/Inference/ComfyClient.cs +++ b/StabilityMatrix.Core/Inference/ComfyClient.cs @@ -471,8 +471,9 @@ public class ComfyClient : InferenceClientBase { var response = await comfyApi.GetObjectInfo(nodeName, cancellationToken).ConfigureAwait(false); - var info = response[nodeName]; - return info.Input.GetOptionalValueAsNestedList(optionName); + var info = response.GetValueOrDefault(nodeName); + + return info?.Input.GetOptionalValueAsNestedList(optionName); } protected override void Dispose(bool disposing) From ecc3b7da2dc38ae1bdee70198ea0f95833627157 Mon Sep 17 00:00:00 2001 From: Ionite Date: Fri, 1 Mar 2024 16:43:51 -0500 Subject: [PATCH 15/94] Add extension manager update installed version info function --- .../Extensions/GitPackageExtensionManager.cs | 25 +++++++++++++++++++ .../Extensions/IPackageExtensionManager.cs | 7 ++++++ 2 files changed, 32 insertions(+) diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs index 1aac49f1..3cc5e17b 100644 --- a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs +++ b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs @@ -201,6 +201,31 @@ public abstract partial class GitPackageExtensionManager(IPrerequisiteHelper pre return extensions; } + public virtual async Task GetInstalledExtensionInfoAsync( + InstalledPackageExtension installedExtension + ) + { + if (installedExtension.PrimaryPath is not DirectoryPath extensionDirectory) + { + return installedExtension; + } + + // Get git version + var version = await prerequisiteHelper + .GetGitRepositoryVersion(extensionDirectory) + .ConfigureAwait(false); + + return installedExtension with + { + Version = new PackageExtensionVersion + { + Tag = version.Tag, + Branch = version.Branch, + CommitSha = version.CommitSha + } + }; + } + /// public virtual async Task InstallExtensionAsync( PackageExtension extension, diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs b/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs index 76903233..45f56017 100644 --- a/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs +++ b/StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs @@ -89,6 +89,13 @@ public interface IPackageExtensionManager CancellationToken cancellationToken = default ); + /// + /// Get updated info (version) for an installed extension. + /// + Task GetInstalledExtensionInfoAsync( + InstalledPackageExtension installedExtension + ); + /// /// Install an extension to the provided package. /// From 54b1e9a2167fd141c1cf6b7a5a0b8a08560a9649 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 2 Mar 2024 01:07:05 -0800 Subject: [PATCH 16/94] Fix unknown model types not showing on checkpoints page --- .../ViewModels/CheckpointManager/CheckpointFolder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs index ab777a44..e11cccdc 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs @@ -111,6 +111,10 @@ public partial class CheckpointFolder : ViewModelBase public IObservableCollection BaseModelOptions { get; } = new ObservableCollectionExtended(); + private IEnumerable allModelOptions = Enum.GetValues() + .Where(x => x != CivitBaseModelType.All) + .Select(x => x.GetStringValue()); + public CheckpointFolder( ISettingsManager settingsManager, IDownloadService downloadService, @@ -175,11 +179,7 @@ public partial class CheckpointFolder : ViewModelBase SubFoldersCache.Refresh(); }); - BaseModelOptionsCache.AddOrUpdate( - Enum.GetValues() - .Where(x => x != CivitBaseModelType.All) - .Select(x => x.GetStringValue()) - ); + BaseModelOptionsCache.AddOrUpdate(allModelOptions); CheckpointFiles.CollectionChanged += OnCheckpointFilesChanged; // DisplayedCheckpointFiles = CheckpointFiles; @@ -187,7 +187,7 @@ public partial class CheckpointFolder : ViewModelBase private bool BaseModelFilter(CheckpointFile file) { - return file.IsConnectedModel + return file.IsConnectedModel && allModelOptions.Contains(file.ConnectedModel!.BaseModel) ? BaseModelOptions.Contains(file.ConnectedModel!.BaseModel) : BaseModelOptions.Contains("Other"); } From cd15b7650557b5886bbeb966ab9762d5011cbb30 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 2 Mar 2024 01:08:00 -0800 Subject: [PATCH 17/94] chagenlog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dcc2910..5edfcdb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.9.0 +### Fixed +- Fixed unknown model types not showing on checkpoints page (thanks Jerry!) + ## v2.9.0-pre.2 ### Added - Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`) From 2303759eeccebe5a5ee69c1b6b00623347d168ca Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 2 Mar 2024 03:44:35 -0800 Subject: [PATCH 18/94] redesigned packages page & made it the first tab & removed old launch page from tabs --- StabilityMatrix.Avalonia/App.axaml.cs | 3 +- .../Styles/ButtonStyles.axaml | 73 +++ .../CheckpointManager/CheckpointFolder.cs | 12 +- .../PackageManager/PackageCardViewModel.cs | 73 ++- .../ViewModels/RunningPackageViewModel.cs | 11 +- .../Views/ConsoleOutputPage.axaml | 8 +- .../PackageInstallBrowserView.axaml | 276 ++++---- .../Views/PackageManagerPage.axaml | 607 +++++++++--------- .../Models/DownloadPackageVersionOptions.cs | 3 + 9 files changed, 621 insertions(+), 445 deletions(-) diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index f1c10ddd..014d25d8 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -328,9 +328,8 @@ public sealed class App : Application { Pages = { - provider.GetRequiredService(), - provider.GetRequiredService(), provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService() diff --git a/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml b/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml index b0af1750..0b8d75f8 100644 --- a/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml +++ b/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml @@ -99,6 +99,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + - - - - - - - - - - - + + + - - - - - - - - - + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml b/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml index d1460a42..999625ac 100644 --- a/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml +++ b/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml @@ -12,12 +12,13 @@ xmlns:system="clr-namespace:System;assembly=System.Runtime" xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" xmlns:avalonia="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + xmlns:markupExtensions="clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions" + mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="450" x:DataType="viewModels:PackageManagerViewModel" x:CompileBindings="True" d:DataContext="{x:Static designData:DesignData.PackageManagerViewModel}" x:Class="StabilityMatrix.Avalonia.Views.PackageManagerPage"> - + @@ -32,216 +33,34 @@ - - + + - - - - - - - - - - - - + - - @@ -252,114 +71,328 @@ TextWrapping="Wrap" Text="{x:Static lang:Resources.Label_UnknownPackage}" /> + + + + + - - - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + IsVisible="{Binding IsProgressVisible}" + RowDefinitions="Auto, *"> + !string.IsNullOrWhiteSpace(VersionTag) ? VersionTag : $"{BranchName}@{CommitHash?[..7]}"; } From d329f37e9d5cc66c893eba3cbfc394890d205b8e Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 2 Mar 2024 14:55:21 -0500 Subject: [PATCH 19/94] Add version requirements for nodes --- .../Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index f408dd96..b54547f4 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -332,7 +332,7 @@ public class ComfyNodeBuilder [TypedNodeOptions( Name = "Inference_Core_PromptExpansion", - RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes"] + RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.2.0"] )] public record PromptExpansion : ComfyTypedNodeBase { @@ -344,7 +344,7 @@ public class ComfyNodeBuilder [TypedNodeOptions( Name = "Inference_Core_AIO_Preprocessor", - RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes"] + RequiredExtensions = ["https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.2.0"] )] public record AIOPreprocessor : ComfyTypedNodeBase { From 96c30f5d407f18abb291b999021ea85a0ff32f93 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 2 Mar 2024 14:56:07 -0500 Subject: [PATCH 20/94] Add out of date extension checks --- .../Base/InferenceGenerationViewModelBase.cs | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs index 2d70b02e..63622a72 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs @@ -17,9 +17,9 @@ using CommunityToolkit.Mvvm.Input; using ExifLibrary; using FluentAvalonia.UI.Controls; using Microsoft.Extensions.DependencyInjection; -using Nito.Disposables.Internals; using NLog; using Refit; +using Semver; using SkiaSharp; using StabilityMatrix.Avalonia.Extensions; using StabilityMatrix.Avalonia.Helpers; @@ -643,12 +643,10 @@ public abstract partial class InferenceGenerationViewModelBase { // Get prompt required extensions // Just static for now but could do manifest lookup when we support custom workflows - var requiredExtensions = nodeDictionary - .ClassTypeRequiredExtensions.Values.SelectMany(x => x) - .ToHashSet(); + var requiredExtensionSpecifiers = nodeDictionary.RequiredExtensions.ToList(); // Skip if no extensions required - if (requiredExtensions.Count == 0) + if (requiredExtensionSpecifiers.Count == 0) { return true; } @@ -661,20 +659,63 @@ public abstract partial class InferenceGenerationViewModelBase await ((GitPackageExtensionManager)manager).GetInstalledExtensionsLiteAsync( localPackagePair.InstalledPackage ) - ).ToImmutableArray(); + ).ToList(); + + var localExtensionsByGitUrl = localExtensions + .Where(ext => ext.GitRepositoryUrl is not null) + .ToDictionary(ext => ext.GitRepositoryUrl!, ext => ext); + + var requiredExtensionReferences = requiredExtensionSpecifiers + .Select(specifier => specifier.Name) + .ToHashSet(); + + var missingExtensions = new List(); + var outOfDateExtensions = + new List<(ExtensionSpecifier Specifier, InstalledPackageExtension Installed)>(); + + // Check missing extensions and out of date extensions + foreach (var specifier in requiredExtensionSpecifiers) + { + if (!localExtensionsByGitUrl.TryGetValue(specifier.Name, out var localExtension)) + { + missingExtensions.Add(specifier); + continue; + } - var missingExtensions = requiredExtensions - .Except(localExtensions.Select(ext => ext.GitRepositoryUrl).WhereNotNull()) - .ToImmutableArray(); + // Check if constraint is specified + if (specifier.Constraint is not null && specifier.TryGetSemVersionRange(out var semVersionRange)) + { + // Get version to compare + localExtension = await manager.GetInstalledExtensionInfoAsync(localExtension); + + // Try to parse local tag to semver + if ( + localExtension.Version?.Tag is not null + && SemVersion.TryParse( + localExtension.Version.Tag, + SemVersionStyles.AllowV, + out var localSemVersion + ) + ) + { + // Check if not satisfied + if (!semVersionRange.Contains(localSemVersion)) + { + outOfDateExtensions.Add((specifier, localExtension)); + } + } + } + } - if (missingExtensions.Length == 0) + if (missingExtensions.Count == 0 && outOfDateExtensions.Count == 0) { return true; } var dialog = DialogHelper.CreateMarkdownDialog( $"#### The following extensions are required for this workflow:\n" - + $"{string.Join("\n- ", missingExtensions)}", + + $"{string.Join("\n- ", missingExtensions.Select(ext => ext.Name))}" + + $"{string.Join("\n- ", outOfDateExtensions.Select(pair => $"{pair.Item1.Name} {pair.Specifier.Constraint} {pair.Specifier.Version} (Current Version: {pair.Installed.Version?.Tag})"))}", "Install Required Extensions?" ); @@ -692,13 +733,13 @@ public abstract partial class InferenceGenerationViewModelBase var steps = new List(); - foreach (var missingExtensionUrl in missingExtensions) + foreach (var missingExtension in missingExtensions) { - if (!manifestExtensionsMap.TryGetValue(missingExtensionUrl, out var extension)) + if (!manifestExtensionsMap.TryGetValue(missingExtension.Name, out var extension)) { Logger.Warn( "Extension {MissingExtensionUrl} not found in manifests", - missingExtensionUrl + missingExtension.Name ); continue; } From 37f1ac512c6a913c71f273a3252ab3bb31df9ec9 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 2 Mar 2024 15:05:21 -0500 Subject: [PATCH 21/94] Add error handling for SelectImage card hashing --- .../Inference/SelectImageCardViewModel.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs index 6e2e7b15..98c637e5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs @@ -90,9 +90,18 @@ public partial class SelectImageCardViewModel(INotificationService notificationS partial void OnImageSourceChanged(ImageSource? value) { // Cache the hash for later upload use - if (value?.LocalFile is { Exists: true }) + if (value?.LocalFile is { Exists: true } localFile) { - value.GetBlake3HashAsync().SafeFireAndForget(); + value + .GetBlake3HashAsync() + .SafeFireAndForget(ex => + { + Logger.Warn(ex, "Error getting hash for image {Path}", localFile.Name); + notificationService.ShowPersistent( + $"Error getting hash for image {localFile.Name}", + $"{ex.GetType().Name}: {ex.Message}" + ); + }); } } From 2862e90622f0ab14ce04ae84c78cf0bd57980ba0 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 2 Mar 2024 15:07:32 -0500 Subject: [PATCH 22/94] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dcc2910..5e3a3dda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.9.0-pre.3 +### Fixed +- Improved error handling for Inference Select Image hash calculation in case file is being written to while being read + ## v2.9.0-pre.2 ### Added - Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`) From 842ab35e7d9f9eef0e39d393ff97142618f2f4c3 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 2 Mar 2024 22:41:57 -0500 Subject: [PATCH 23/94] Add Inference ControlNet preprocessor dimensions --- .../Controls/Inference/ControlNetCard.axaml | 43 ++++++++++++++++++- .../Inference/ControlNetCardViewModel.cs | 26 ++++++++++- .../Inference/Modules/ControlNetModule.cs | 4 +- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml index cb3b8567..b376b780 100644 --- a/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml @@ -36,12 +36,12 @@ + + + + + + + + (); + + // Update our width and height when the image changes + SelectImageCardViewModel + .WhenPropertyChanged(card => card.CurrentBitmapSize) + .Subscribe(propertyValue => + { + if (!propertyValue.Value.IsEmpty) + { + Width = propertyValue.Value.Width; + Height = propertyValue.Value.Height; + } + }); } [RelayCommand] diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs index f681f053..e4845fc6 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs @@ -56,7 +56,9 @@ public class ControlNetModule : ModuleBase { Name = e.Nodes.GetUniqueName("ControlNet_Preprocessor"), Image = image, - Preprocessor = preprocessor.RelativePath + Preprocessor = preprocessor.RelativePath, + // Use width if valid, else default of 512 + Resolution = card.Width is <= 2048 and > 0 ? card.Width : 512 } ); From 8f53c281b3abde9fcc061881443429f9addf921b Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 3 Mar 2024 02:25:49 -0800 Subject: [PATCH 24/94] updated one-click stuff & confirm exit dialog for multi-package --- .../Languages/Resources.Designer.cs | 36 +++++++++++++++ .../Languages/Resources.resx | 12 +++++ .../Base/InferenceGenerationViewModelBase.cs | 12 ++--- .../Base/InferenceTabViewModelBase.cs | 16 ++----- .../InferenceImageToImageViewModel.cs | 20 +++++--- .../InferenceImageToVideoViewModel.cs | 5 +- .../InferenceImageUpscaleViewModel.cs | 15 ++++-- .../InferenceTextToImageViewModel.cs | 5 +- .../ViewModels/MainWindowViewModel.cs | 8 ---- .../ViewModels/PackageManagerViewModel.cs | 12 ++--- .../Views/MainWindow.axaml.cs | 46 +++++++++++++++++-- .../Views/PackageManagerPage.axaml | 26 ++++++++++- .../Views/PackageManagerPage.axaml.cs | 33 ++++++++++++- 13 files changed, 194 insertions(+), 52 deletions(-) diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs index c56d773b..4802a3a5 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs +++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs @@ -959,6 +959,24 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Confirm Exit. + /// + public static string Label_ConfirmExit { + get { + return ResourceManager.GetString("Label_ConfirmExit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Are you sure you want to exit? This will also close any currently running packages.. + /// + public static string Label_ConfirmExitDetail { + get { + return ResourceManager.GetString("Label_ConfirmExitDetail", resourceCulture); + } + } + /// /// Looks up a localized string similar to Confirm Password. /// @@ -1004,6 +1022,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Console. + /// + public static string Label_Console { + get { + return ResourceManager.GetString("Label_Console", resourceCulture); + } + } + /// /// Looks up a localized string similar to This will move all generated images from the selected packages to the Consolidated directory of the shared outputs folder. This action cannot be undone.. /// @@ -2399,6 +2426,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Web UI. + /// + public static string Label_WebUi { + get { + return ResourceManager.GetString("Label_WebUi", resourceCulture); + } + } + /// /// Looks up a localized string similar to Width. /// diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx index a2129eaf..83430fee 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.resx @@ -978,4 +978,16 @@ Auto-Scroll to End + + Confirm Exit + + + Are you sure you want to exit? This will also close any currently running packages. + + + Console + + + Web UI + diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs index 2d70b02e..9fe9cb40 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs @@ -59,6 +59,7 @@ public abstract partial class InferenceGenerationViewModelBase private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly ISettingsManager settingsManager; + private readonly RunningPackageService runningPackageService; private readonly INotificationService notificationService; private readonly ServiceManager vmFactory; @@ -79,12 +80,14 @@ public abstract partial class InferenceGenerationViewModelBase ServiceManager vmFactory, IInferenceClientManager inferenceClientManager, INotificationService notificationService, - ISettingsManager settingsManager + ISettingsManager settingsManager, + RunningPackageService runningPackageService ) : base(notificationService) { this.notificationService = notificationService; this.settingsManager = settingsManager; + this.runningPackageService = runningPackageService; this.vmFactory = vmFactory; ClientManager = inferenceClientManager; @@ -722,15 +725,12 @@ public abstract partial class InferenceGenerationViewModelBase return; // Restart Package - // TODO: This should be handled by some DI package manager service - var launchPage = App.Services.GetRequiredService(); - try { await Dispatcher.UIThread.InvokeAsync(async () => { - await launchPage.Stop(); - await launchPage.LaunchAsync(); + await runningPackageService.StopPackage(localPackagePair.InstalledPackage.Id); + await runningPackageService.StartPackage(localPackagePair.InstalledPackage); }); } catch (Exception e) diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs index 63e5b131..57e6bf47 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs @@ -98,11 +98,7 @@ public abstract partial class InferenceTabViewModelBase protected async Task SaveViewState() { var eventArgs = new SaveViewStateEventArgs(); - saveViewStateRequestedEventManager?.RaiseEvent( - this, - eventArgs, - nameof(SaveViewStateRequested) - ); + saveViewStateRequestedEventManager?.RaiseEvent(this, eventArgs, nameof(SaveViewStateRequested)); if (eventArgs.StateTask is not { } stateTask) { @@ -128,7 +124,7 @@ public abstract partial class InferenceTabViewModelBase // TODO: Dock reset not working, using this hack for now to get a new view var navService = App.Services.GetRequiredService>(); - navService.NavigateTo(new SuppressNavigationTransitionInfo()); + navService.NavigateTo(new SuppressNavigationTransitionInfo()); ((IPersistentViewProvider)this).AttachedPersistentView = null; navService.NavigateTo(new BetterEntranceNavigationTransition()); } @@ -157,9 +153,7 @@ public abstract partial class InferenceTabViewModelBase if (result == ContentDialogResult.Primary && textFields[0].Text is { } json) { - LoadViewState( - new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } } - ); + LoadViewState(new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } }); } } @@ -226,9 +220,7 @@ public abstract partial class InferenceTabViewModelBase if (this is IParametersLoadableState paramsLoadableVm) { - Dispatcher.UIThread.Invoke( - () => paramsLoadableVm.LoadStateFromParameters(parameters) - ); + Dispatcher.UIThread.Invoke(() => paramsLoadableVm.LoadStateFromParameters(parameters)); } else { diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs index 50c4734f..77c5230f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs @@ -27,9 +27,17 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel IInferenceClientManager inferenceClientManager, INotificationService notificationService, ISettingsManager settingsManager, - IModelIndexService modelIndexService + IModelIndexService modelIndexService, + RunningPackageService runningPackageService ) - : base(notificationService, inferenceClientManager, settingsManager, vmFactory, modelIndexService) + : base( + notificationService, + inferenceClientManager, + settingsManager, + vmFactory, + modelIndexService, + runningPackageService + ) { SelectImageCardViewModel = vmFactory.Get(); @@ -77,12 +85,12 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel var mainImages = SelectImageCardViewModel.GetInputImages(); var samplerImages = SamplerCardViewModel - .ModulesCardViewModel - .Cards - .OfType() + .ModulesCardViewModel.Cards.OfType() .SelectMany(m => m.GetInputImages()); - var moduleImages = ModulesCardViewModel.Cards.OfType().SelectMany(m => m.GetInputImages()); + var moduleImages = ModulesCardViewModel + .Cards.OfType() + .SelectMany(m => m.GetInputImages()); return mainImages.Concat(samplerImages).Concat(moduleImages); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs index b57e3406..035774c1 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs @@ -65,9 +65,10 @@ public partial class InferenceImageToVideoViewModel IInferenceClientManager inferenceClientManager, ISettingsManager settingsManager, ServiceManager vmFactory, - IModelIndexService modelIndexService + IModelIndexService modelIndexService, + RunningPackageService runningPackageService ) - : base(vmFactory, inferenceClientManager, notificationService, settingsManager) + : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService) { this.notificationService = notificationService; this.modelIndexService = modelIndexService; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs index 2ca1dbd8..540de0d9 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs @@ -59,9 +59,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase INotificationService notificationService, IInferenceClientManager inferenceClientManager, ISettingsManager settingsManager, - ServiceManager vmFactory + ServiceManager vmFactory, + RunningPackageService runningPackageService ) - : base(vmFactory, inferenceClientManager, notificationService, settingsManager) + : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService) { this.notificationService = notificationService; @@ -142,7 +143,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase } /// - protected override async Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken) + protected override async Task GenerateImageImpl( + GenerateOverrides overrides, + CancellationToken cancellationToken + ) { if (!ClientManager.IsConnected) { @@ -169,7 +173,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase Client = ClientManager.Client, Nodes = buildPromptArgs.Builder.ToNodeDictionary(), OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(), - Parameters = new GenerationParameters { ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name, }, + Parameters = new GenerationParameters + { + ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name, + }, Project = InferenceProjectDocument.FromLoadable(this) }; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs index 12d0d5fc..a1924559 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs @@ -59,9 +59,10 @@ public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, I IInferenceClientManager inferenceClientManager, ISettingsManager settingsManager, ServiceManager vmFactory, - IModelIndexService modelIndexService + IModelIndexService modelIndexService, + RunningPackageService runningPackageService ) - : base(vmFactory, inferenceClientManager, notificationService, settingsManager) + : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService) { this.notificationService = notificationService; this.modelIndexService = modelIndexService; diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index 5aec5637..718cd574 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs @@ -139,14 +139,6 @@ public partial class MainWindowViewModel : ViewModelBase Content = new NewOneClickInstallDialog { DataContext = viewModel }, }; - EventManager.Instance.OneClickInstallFinished += (_, skipped) => - { - if (skipped) - return; - - EventManager.Instance.OnTeachingTooltipNeeded(); - }; - var firstDialogResult = await dialog.ShowAsync(App.TopLevel); if (firstDialogResult != ContentDialogResult.Primary) diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs index 6066fd02..df3c33a8 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs @@ -6,8 +6,6 @@ using System.Linq; using System.Threading.Tasks; using AsyncAwaitBestPractices; using Avalonia.Controls; -using Avalonia.Controls.Notifications; -using Avalonia.Controls.Primitives; using Avalonia.Threading; using CommunityToolkit.Mvvm.Input; using DynamicData; @@ -15,18 +13,14 @@ using DynamicData.Binding; using FluentAvalonia.UI.Controls; using Microsoft.Extensions.Logging; using StabilityMatrix.Avalonia.Animations; -using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; -using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Avalonia.ViewModels.PackageManager; using StabilityMatrix.Avalonia.Views; -using StabilityMatrix.Avalonia.Views.Dialogs; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.FileInterfaces; -using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.Services; using Symbol = FluentIcons.Common.Symbol; @@ -84,6 +78,7 @@ public partial class PackageManagerViewModel : PageViewModelBase this.logger = logger; EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged; + EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished; var installed = installedPackages.Connect(); var unknown = unknownInstalledPackages.Connect(); @@ -107,6 +102,11 @@ public partial class PackageManagerViewModel : PageViewModelBase timer.Tick += async (_, _) => await CheckPackagesForUpdates(); } + private void OnOneClickInstallFinished(object? sender, bool e) + { + OnLoadedAsync().SafeFireAndForget(); + } + public void SetPackages(IEnumerable packages) { installedPackages.Edit(s => s.Load(packages)); diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs index 614754a6..c720f37b 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Reactive.Linq; using System.Threading; +using AsyncAwaitBestPractices; using AsyncImageLoader; using Avalonia; using Avalonia.Controls; @@ -193,13 +194,52 @@ public partial class MainWindow : AppWindowBase protected override void OnClosing(WindowClosingEventArgs e) { // Show confirmation if package running - var launchPageViewModel = App.Services.GetRequiredService(); - - launchPageViewModel.OnMainWindowClosing(e); + var runningPackageService = App.Services.GetRequiredService(); + if ( + runningPackageService.RunningPackages.Count > 0 + && e.CloseReason is WindowCloseReason.WindowClosing + ) + { + e.Cancel = true; + + var dialog = CreateExitConfirmDialog(); + Dispatcher + .UIThread.InvokeAsync(async () => + { + if ( + (TaskDialogStandardResult)await dialog.ShowAsync(true) == TaskDialogStandardResult.Yes + ) + { + App.Services.GetRequiredService().Hide(); + App.Shutdown(); + } + }) + .SafeFireAndForget(); + } base.OnClosing(e); } + private static TaskDialog CreateExitConfirmDialog() + { + var dialog = DialogHelper.CreateTaskDialog( + Languages.Resources.Label_ConfirmExit, + Languages.Resources.Label_ConfirmExitDetail + ); + + dialog.ShowProgressBar = false; + dialog.FooterVisibility = TaskDialogFooterVisibility.Never; + + dialog.Buttons = new List + { + new("Exit", TaskDialogStandardResult.Yes), + TaskDialogButton.CancelButton + }; + dialog.Buttons[0].IsDefault = true; + + return dialog; + } + /// protected override void OnClosed(EventArgs e) { diff --git a/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml b/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml index 999625ac..81c912e6 100644 --- a/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml +++ b/StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml @@ -26,6 +26,7 @@ @@ -284,6 +285,17 @@ + + + - + - - - - + @@ -161,7 +136,8 @@ - + - + From d5f726d9d66ef7c0874162115dba2f60223cbba1 Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 6 Mar 2024 19:51:02 -0500 Subject: [PATCH 37/94] Add DebugExtractImagePromptsToTxt Command --- .../Settings/MainSettingsViewModel.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs index 642d5d5a..8fdfe91d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs @@ -777,7 +777,8 @@ public partial class MainSettingsViewModel : PageViewModelBase new CommandItem(DebugExtractDmgCommand), new CommandItem(DebugShowNativeNotificationCommand), new CommandItem(DebugClearImageCacheCommand), - new CommandItem(DebugGCCollectCommand) + new CommandItem(DebugGCCollectCommand), + new CommandItem(DebugExtractImagePromptsToTxtCommand) ]; [RelayCommand] @@ -907,6 +908,45 @@ public partial class MainSettingsViewModel : PageViewModelBase GC.Collect(); } + [RelayCommand] + private async Task DebugExtractImagePromptsToTxt() + { + // Choose images + var provider = App.StorageProvider; + var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions { AllowMultiple = true }); + + if (files.Count == 0) + return; + + var images = await Task.Run( + () => files.Select(f => LocalImageFile.FromPath(f.TryGetLocalPath()!)).ToList() + ); + + var successfulFiles = new List(); + + foreach (var localImage in images) + { + var imageFile = new FilePath(localImage.AbsolutePath); + + // Write a txt with the same name as the image + var txtFile = imageFile.WithName(imageFile.NameWithoutExtension + ".txt"); + + // Read metadata + if (localImage.GenerationParameters?.PositivePrompt is { } positivePrompt) + { + await File.WriteAllTextAsync(txtFile, positivePrompt); + + successfulFiles.Add(localImage); + } + } + + notificationService.Show( + "Extracted prompts", + $"Extracted prompts from {successfulFiles.Count}/{images.Count} images.", + NotificationType.Success + ); + } + #endregion #region Info Section From 09c72677b7547f294d5af39bed675882c4660963 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 6 Mar 2024 19:00:09 -0800 Subject: [PATCH 38/94] fix crash when directory not exist --- StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs index 0728a792..a2685a24 100644 --- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs @@ -596,6 +596,10 @@ public partial class OutputsPageViewModel : PageViewModelBase private ObservableCollection GetSubfolders(string strPath) { var subfolders = new ObservableCollection(); + + if (!Directory.Exists(strPath)) + return subfolders; + var directories = Directory.EnumerateDirectories(strPath, "*", SearchOption.TopDirectoryOnly); foreach (var dir in directories) From b394e5b701c94c933d0039cc006ce95548dd356b Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 6 Mar 2024 21:36:32 -0800 Subject: [PATCH 39/94] faster outputs page with Task.Run & avalonia.labs async image --- StabilityMatrix.Avalonia/App.axaml | 2 + .../SelectableImageButton.axaml | 5 +- .../SelectableImageButton.cs | 8 ++- .../ViewModels/OutputsPageViewModel.cs | 70 ++++++++++++++----- .../Views/OutputsPage.axaml | 43 ++++++++++-- .../Models/Database/LocalImageFile.cs | 2 + 6 files changed, 100 insertions(+), 30 deletions(-) diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index a1bf280c..fa669962 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -4,6 +4,7 @@ xmlns:local="using:StabilityMatrix.Avalonia" xmlns:idcr="using:Dock.Avalonia.Controls.Recycling" xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia" + xmlns:controls="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls" Name="Stability Matrix" RequestedThemeVariant="Dark"> @@ -80,6 +81,7 @@ + + + + @@ -161,13 +190,15 @@ - + - + @@ -177,7 +208,7 @@ ImageHeight="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ImageSize.Height}" ImageWidth="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ImageSize.Width}" IsSelected="{Binding IsSelected}" - Source="{Binding ImageFile.AbsolutePath}"> + Source="{Binding ImageFile.AbsolutePathUriString}"> public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(AbsolutePath); + public Uri AbsolutePathUriString => new($"file://{AbsolutePath}"); + public (string? Parameters, string? ParametersJson, string? SMProject, string? ComfyNodes) ReadMetadata() { if (AbsolutePath.EndsWith("webp")) From aed6e79957c3cf1d54a1baa94910164f44f7a1fe Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 6 Mar 2024 21:47:59 -0800 Subject: [PATCH 40/94] chagenlog --- CHANGELOG.md | 4 ++++ StabilityMatrix.Avalonia/Views/OutputsPage.axaml | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3acb129..01d5ba5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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-dev.1 +### Changed +- Changed the Outputs Page to use a TreeView for the directory selection instead of a dropdown selector + ## v2.9.0 ### Added - Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml index 46b81990..b679e541 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml @@ -54,12 +54,7 @@ - - + Date: Wed, 6 Mar 2024 21:50:22 -0800 Subject: [PATCH 41/94] moar chagenlog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01d5ba5a..f1472e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.10.0-dev.1 ### Changed +- Revamped the Packages page to enable running multiple packages at the same time - Changed the Outputs Page to use a TreeView for the directory selection instead of a dropdown selector +### Removed +- Removed the main Launch page, as it is no longer needed with the new Packages page ## v2.9.0 ### Added From 6fb5b0cb17514bf2902238f7afcad7215d67dd7f Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 6 Mar 2024 22:01:40 -0800 Subject: [PATCH 42/94] add the treeview back --- .../Views/OutputsPage.axaml | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml index dcc05e1c..ad145027 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml @@ -16,8 +16,6 @@ xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" - xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" - xmlns:system="clr-namespace:System;assembly=System.Runtime" d:DataContext="{x:Static mocks:DesignData.OutputsPageViewModel}" d:DesignHeight="650" d:DesignWidth="800" @@ -25,20 +23,29 @@ x:DataType="vm:OutputsPageViewModel" Focusable="True" mc:Ignorable="d"> - - - - loading - - - - - - + + + + + + + + + + + + + + + + +