From bc23cc638d373c5cc830adb9fca61b59693aa471 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 29 Dec 2024 17:28:40 -0800 Subject: [PATCH 01/11] Merge pull request #937 from ionite34/fix-inference-buttons Fix Inference image select card buttons taking up the whole height (cherry picked from commit ebf37c7f67947edfc727ea95a6711f597d863395) --- CHANGELOG.md | 8 ++++ .../Controls/Inference/SelectImageCard.axaml | 42 +++++++++++-------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1b8fb1e..7ea9d4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +## v2.14.0-dev.1 +### Fixed +- Fixed Inference image selector card buttons taking up the whole height of the card + +## v2.13.1 +### Fixed +- Fixed Inference image selector card buttons taking up the whole height of the card + ## v2.13.0 ### Added - Added new package - [ComfyUI-Zluda](https://github.com/patientx/ComfyUI-Zluda) - for AMD GPU users on Windows diff --git a/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml index 0aa53b84..bf6de24c 100644 --- a/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml @@ -48,7 +48,7 @@ Source="{Binding ImageSource}" Stretch="Uniform" StretchDirection="Both" /> - + @@ -57,13 +57,13 @@ - + RenderOptions.BitmapInterpolationMode="HighQuality" + Source="{Binding CachedOrNewMaskRenderImage.Bitmap}" + Stretch="Uniform" + StretchDirection="Both" /> @@ -102,16 +102,18 @@ - + IsVisible="{Binding !IsSelectionAvailable}" + Orientation="Horizontal" + Spacing="4"> + + CornerRadius="10" + IsVisible="{Binding IsMaskEditorEnabled}"> + FontSize="{TemplateBinding FontSize}" + IsChecked="{Binding IsMaskOverlayEnabled}"> @@ -142,11 +144,13 @@ Symbol="Eye" /> - + + CornerRadius="10" + IsVisible="{Binding IsMaskEditorEnabled}"> - + From 25ca06d581144291f10fae744ef9c024b8bd035f Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Jan 2025 00:46:56 -0800 Subject: [PATCH 02/11] chagenlog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ea9d4eb..46e091d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,6 @@ 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.14.0-dev.1 -### Fixed -- Fixed Inference image selector card buttons taking up the whole height of the card - ## v2.13.1 ### Fixed - Fixed Inference image selector card buttons taking up the whole height of the card From e4df25e8739fc08ab86140984eeb3a641112532a Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Jan 2025 01:04:58 -0800 Subject: [PATCH 03/11] Merge pull request #942 from ionite34/fix-call-from-invalid-thread Fix call from invalid thread crash on one-click install (cherry picked from commit 28439de02ee9d14261b312cbee7e73718deef4e2) --- CHANGELOG.md | 1 + .../Dialogs/NewOneClickInstallViewModel.cs | 224 +++++++++--------- .../MainPackageManagerViewModel.cs | 2 +- 3 files changed, 110 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46e091d7..3ffe4509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.13.1 ### Fixed +- Fixed [#1078](https://github.com/LykosAI/StabilityMatrix/issues/1078) - "Call from invalid thread" error after one-click install finishes - Fixed Inference image selector card buttons taking up the whole height of the card ## v2.13.0 diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs index 9f31c350..623d43db 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs @@ -87,146 +87,138 @@ INotificationService notificationService .ThenByAscending(p => p.DisplayName) ) .ObserveOn(SynchronizationContext.Current) - .Subscribe(); + .Subscribe(_ => + { + if (ShownPackages.Count > 0) + return; - AllPackagesCache.AddOrUpdate(packageFactory.GetAllAvailablePackages()); - if (ShownPackages.Count > 0) - return; + ShowIncompatiblePackages = true; + }); - ShowIncompatiblePackages = true; + AllPackagesCache.AddOrUpdate(packageFactory.GetAllAvailablePackages()); } [RelayCommand] - private void InstallComfyForInference() + private async Task InstallComfyForInference() { var comfyPackage = ShownPackages.FirstOrDefault(x => x is ComfyUI); if (comfyPackage == null) return; isInferenceInstall = true; - InstallPackage(comfyPackage); + await InstallPackage(comfyPackage); } [RelayCommand] - private void InstallPackage(BasePackage selectedPackage) + private async Task InstallPackage(BasePackage selectedPackage) { - Task.Run(async () => - { - var installLocation = Path.Combine( - settingsManager.LibraryDir, - "Packages", - selectedPackage.Name - ); - - var steps = new List - { - new SetPackageInstallingStep(settingsManager, selectedPackage.Name), - new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage), - }; - - // get latest version & download & install - if (Directory.Exists(installLocation)) - { - var installPath = new DirectoryPath(installLocation); - await installPath.DeleteVerboseAsync(logger); - } - - var downloadVersion = await selectedPackage.GetLatestVersion(); - var installedVersion = new InstalledPackageVersion { IsPrerelease = false }; + OnPrimaryButtonClick(); - if (selectedPackage.ShouldIgnoreReleases) - { - installedVersion.InstalledBranch = downloadVersion.BranchName; - installedVersion.InstalledCommitSha = downloadVersion.CommitHash; - } - else - { - installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag; - } + var installLocation = Path.Combine(settingsManager.LibraryDir, "Packages", selectedPackage.Name); + + var steps = new List + { + new SetPackageInstallingStep(settingsManager, selectedPackage.Name), + new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage) + }; + + // get latest version & download & install + if (Directory.Exists(installLocation)) + { + var installPath = new DirectoryPath(installLocation); + await installPath.DeleteVerboseAsync(logger); + } + + var downloadVersion = await selectedPackage.GetLatestVersion(); + var installedVersion = new InstalledPackageVersion { IsPrerelease = false }; + + if (selectedPackage.ShouldIgnoreReleases) + { + installedVersion.InstalledBranch = downloadVersion.BranchName; + installedVersion.InstalledCommitSha = downloadVersion.CommitHash; + } + else + { + installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag; + } + + var torchVersion = selectedPackage.GetRecommendedTorchVersion(); + var recommendedSharedFolderMethod = selectedPackage.RecommendedSharedFolderMethod; + + var installedPackage = new InstalledPackage + { + DisplayName = selectedPackage.DisplayName, + LibraryPath = Path.Combine("Packages", selectedPackage.Name), + Id = Guid.NewGuid(), + PackageName = selectedPackage.Name, + Version = installedVersion, + LaunchCommand = selectedPackage.LaunchCommand, + LastUpdateCheck = DateTimeOffset.Now, + PreferredTorchIndex = torchVersion, + PreferredSharedFolderMethod = recommendedSharedFolderMethod + }; + + var downloadStep = new DownloadPackageVersionStep( + selectedPackage, + installLocation, + new DownloadPackageOptions { VersionOptions = downloadVersion } + ); + steps.Add(downloadStep); + + var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(Path.Combine(installLocation, "venv")); + steps.Add(unpackSiteCustomizeStep); + + var installStep = new InstallPackageStep( + selectedPackage, + installLocation, + installedPackage, + new InstallPackageOptions + { + SharedFolderMethod = recommendedSharedFolderMethod, + VersionOptions = downloadVersion, + PythonOptions = { TorchIndex = torchVersion } + } + ); + steps.Add(installStep); + + var setupModelFoldersStep = new SetupModelFoldersStep( + selectedPackage, + recommendedSharedFolderMethod, + installLocation + ); + steps.Add(setupModelFoldersStep); + + var setupOutputSharingStep = new SetupOutputSharingStep(selectedPackage, installLocation); + steps.Add(setupOutputSharingStep); + + var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, installedPackage); + steps.Add(addInstalledPackageStep); + + var runner = new PackageModificationRunner + { + ShowDialogOnStart = false, + HideCloseButton = false, + ModificationCompleteMessage = $"{selectedPackage.DisplayName} installed successfully" + }; + + runner + .ExecuteSteps(steps) + .ContinueWith(_ => + { + notificationService.OnPackageInstallCompleted(runner); - var torchVersion = selectedPackage.GetRecommendedTorchVersion(); - var recommendedSharedFolderMethod = selectedPackage.RecommendedSharedFolderMethod; + EventManager.Instance.OnOneClickInstallFinished(false); - var installedPackage = new InstalledPackage - { - DisplayName = selectedPackage.DisplayName, - LibraryPath = Path.Combine("Packages", selectedPackage.Name), - Id = Guid.NewGuid(), - PackageName = selectedPackage.Name, - Version = installedVersion, - LaunchCommand = selectedPackage.LaunchCommand, - LastUpdateCheck = DateTimeOffset.Now, - PreferredTorchIndex = torchVersion, - PreferredSharedFolderMethod = recommendedSharedFolderMethod - }; - - var downloadStep = new DownloadPackageVersionStep( - selectedPackage, - installLocation, - new DownloadPackageOptions { VersionOptions = downloadVersion } - ); - steps.Add(downloadStep); - - var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep( - Path.Combine(installLocation, "venv") - ); - steps.Add(unpackSiteCustomizeStep); - - var installStep = new InstallPackageStep( - selectedPackage, - installLocation, - installedPackage, - new InstallPackageOptions - { - SharedFolderMethod = recommendedSharedFolderMethod, - VersionOptions = downloadVersion, - PythonOptions = { TorchIndex = torchVersion } - } - ); - steps.Add(installStep); - - var setupModelFoldersStep = new SetupModelFoldersStep( - selectedPackage, - recommendedSharedFolderMethod, - installLocation - ); - steps.Add(setupModelFoldersStep); - - var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, installedPackage); - steps.Add(addInstalledPackageStep); + if (!isInferenceInstall) + return; Dispatcher.UIThread.Post(() => { - var runner = new PackageModificationRunner - { - ShowDialogOnStart = false, - HideCloseButton = false, - ModificationCompleteMessage = $"{selectedPackage.DisplayName} installed successfully" - }; - - runner - .ExecuteSteps(steps) - .ContinueWith(_ => - { - notificationService.OnPackageInstallCompleted(runner); - - EventManager.Instance.OnOneClickInstallFinished(false); - - if (!isInferenceInstall) - return; - - Dispatcher.UIThread.Post(() => - { - navigationService.NavigateTo(); - }); - }) - .SafeFireAndForget(); - - EventManager.Instance.OnPackageInstallProgressAdded(runner); + navigationService.NavigateTo(); }); }) .SafeFireAndForget(); - OnPrimaryButtonClick(); + EventManager.Instance.OnPackageInstallProgressAdded(runner); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs index 67b98c1d..c701142b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs @@ -114,7 +114,7 @@ RunningPackageService runningPackageService private void OnOneClickInstallFinished(object? sender, bool e) { - OnLoadedAsync().SafeFireAndForget(); + Dispatcher.UIThread.Post(() => OnLoadedAsync().SafeFireAndForget()); } public void SetPackages(IEnumerable packages) From a5ac8a5df415f94bf771b94fa206881fb4950ea3 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Jan 2025 18:54:28 -0800 Subject: [PATCH 04/11] Merge pull request #946 from ionite34/fix-checkpoint-filters Fix & redesign checkpoint manager base model filtering (cherry picked from commit ef7bc395420c7f43005f07a5818236a24131af95) # Conflicts: # CHANGELOG.md # StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs --- CHANGELOG.md | 21 ++ .../Services/CivitBaseModelTypeService.cs | 92 +++++ .../Services/ICivitBaseModelTypeService.cs | 7 + .../ViewModels/Base/PageViewModelBase.cs | 4 +- .../CivitAiBrowserViewModel.cs | 31 +- .../ViewModels/CheckpointsPageViewModel.cs | 318 ++++++++++-------- .../Views/CheckpointsPage.axaml | 28 +- .../Database/ILiteDbContext.cs | 4 + .../Database/LiteDbContext.cs | 13 + .../Database/CivitBaseModelTypeCacheEntry.cs | 8 + .../Models/Settings/Settings.cs | 6 +- 11 files changed, 337 insertions(+), 195 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs create mode 100644 StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs create mode 100644 StabilityMatrix.Core/Models/Database/CivitBaseModelTypeCacheEntry.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ffe4509..a97dbb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,30 @@ 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). +<<<<<<< HEAD +======= + +## v2.14.0-dev.1 +### Added +- Added Rescale CFG addon to Inference +- Added Swap Dimensions button between the width/height input in Inference +### Changed +- Improved the quality of Inference inpainting by upgrading the workflow behind the scenes. The workflow remains the same for you — just better results! +- Redesigned the Checkpoint Manager Filter flyout to include more options and improve the layout +### Fixed +- Fixed Inference image selector card buttons taking up the whole height of the card +- Fixed Inference mask editor failing to paint to the right-most edge on large images +- Fixed Inference mask editor not showing the entire image in certain circumstances +- Fixed [#1078](https://github.com/LykosAI/StabilityMatrix/issues/1078) - "Call from invalid thread" error after one-click install finishes +- Fixed [#1080](https://github.com/LykosAI/StabilityMatrix/issues/1080) - Some models not displayed in Checkpoint Manager + +>>>>>>> ef7bc395 (Merge pull request #946 from ionite34/fix-checkpoint-filters) ## v2.13.1 +### Changed +- Redesigned the Checkpoint Manager Filter flyout to include more options and improve the layout ### Fixed - Fixed [#1078](https://github.com/LykosAI/StabilityMatrix/issues/1078) - "Call from invalid thread" error after one-click install finishes +- Fixed [#1080](https://github.com/LykosAI/StabilityMatrix/issues/1080) - Some models not displayed in Checkpoint Manager - Fixed Inference image selector card buttons taking up the whole height of the card ## v2.13.0 diff --git a/StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs b/StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs new file mode 100644 index 00000000..9f91ff25 --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs @@ -0,0 +1,92 @@ +using System.Text.Json.Nodes; +using Injectio.Attributes; +using Microsoft.Extensions.Logging; +using StabilityMatrix.Core.Api; +using StabilityMatrix.Core.Database; +using StabilityMatrix.Core.Models.Api; +using StabilityMatrix.Core.Models.Database; + +namespace StabilityMatrix.Avalonia.Services; + +[RegisterSingleton] +public class CivitBaseModelTypeService( + ILogger logger, + ICivitApi civitApi, + ILiteDbContext dbContext +) : ICivitBaseModelTypeService +{ + private const string CacheId = "BaseModelTypes"; + private static readonly TimeSpan CacheExpiration = TimeSpan.FromHours(24); + + /// + /// Gets the list of base model types, using cache if available and not expired + /// + public async Task> GetBaseModelTypes(bool forceRefresh = false, bool includeAllOption = true) + { + List civitBaseModels = []; + + if (!forceRefresh) + { + var cached = await dbContext.GetCivitBaseModelTypeCacheEntry(CacheId); + if (cached != null && DateTimeOffset.UtcNow.Subtract(cached.CreatedAt) < CacheExpiration) + { + civitBaseModels = cached.ModelTypes; + } + } + + try + { + if (civitBaseModels.Count <= 0) + { + var baseModelsResponse = await civitApi.GetBaseModelList(); + var jsonContent = await baseModelsResponse.Content.ReadAsStringAsync(); + var baseModels = JsonNode.Parse(jsonContent); + + var jArray = + baseModels?["error"]?["issues"]?[0]?["unionErrors"]?[0]?["issues"]?[0]?["options"] + as JsonArray; + + civitBaseModels = jArray?.GetValues().ToList() ?? []; + + // Cache the results + var cacheEntry = new CivitBaseModelTypeCacheEntry + { + Id = CacheId, + ModelTypes = civitBaseModels, + CreatedAt = DateTimeOffset.UtcNow + }; + + await dbContext.UpsertCivitBaseModelTypeCacheEntry(cacheEntry); + } + + if (includeAllOption) + { + civitBaseModels.Insert(0, CivitBaseModelType.All.ToString()); + } + + // Filter and sort results + var filteredResults = civitBaseModels + .Where(s => !s.Equals("odor", StringComparison.OrdinalIgnoreCase)) + .OrderBy(s => s) + .ToList(); + + return filteredResults; + } + catch (Exception e) + { + logger.LogError(e, "Failed to get base model list"); + + // Return cached results if available, even if expired + var expiredCache = await dbContext.GetCivitBaseModelTypeCacheEntry(CacheId); + return expiredCache?.ModelTypes ?? []; + } + } + + /// + /// Clears the cached base model types + /// + public void ClearCache() + { + dbContext.CivitBaseModelTypeCache.DeleteAllAsync(); + } +} diff --git a/StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs b/StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs new file mode 100644 index 00000000..9dc58a80 --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs @@ -0,0 +1,7 @@ +namespace StabilityMatrix.Avalonia.Services; + +public interface ICivitBaseModelTypeService +{ + Task> GetBaseModelTypes(bool forceRefresh = false, bool includeAllOption = true); + void ClearCache(); +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs index c1261a8c..3d1da920 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs @@ -5,7 +5,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; /// /// An abstract class for enabling page navigation. /// -public abstract class PageViewModelBase : ViewModelBase +public abstract class PageViewModelBase : DisposableViewModelBase { /// /// Gets if the user can navigate to the next page @@ -16,7 +16,7 @@ public abstract class PageViewModelBase : ViewModelBase /// Gets if the user can navigate to the previous page /// public virtual bool CanNavigatePrevious { get; protected set; } - + public abstract string Title { get; } public abstract IconSource IconSource { get; } } diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs index 3f11591a..309accaa 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs @@ -45,6 +45,7 @@ public sealed partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinit private readonly ISettingsManager settingsManager; private readonly ILiteDbContext liteDbContext; private readonly INotificationService notificationService; + private readonly ICivitBaseModelTypeService baseModelTypeService; private bool dontSearch = false; private readonly SourceCache, int> modelCache = new(static ov => ov.Value.Id); @@ -126,13 +127,15 @@ public CivitAiBrowserViewModel( ISettingsManager settingsManager, ServiceManager dialogFactory, ILiteDbContext liteDbContext, - INotificationService notificationService + INotificationService notificationService, + ICivitBaseModelTypeService baseModelTypeService ) { this.civitApi = civitApi; this.settingsManager = settingsManager; this.liteDbContext = liteDbContext; this.notificationService = notificationService; + this.baseModelTypeService = baseModelTypeService; EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested; @@ -727,31 +730,7 @@ private void UpdateResultsText() [Localizable(false)] private async Task> GetBaseModelList() { - try - { - var baseModelsResponse = await civitApi.GetBaseModelList(); - var jsonContent = await baseModelsResponse.Content.ReadAsStringAsync(); - var baseModels = JsonNode.Parse(jsonContent); - - var jArray = - baseModels?["error"]?["issues"]?[0]?["unionErrors"]?[0]?["issues"]?[0]?["options"] - as JsonArray; - var civitBaseModels = jArray?.GetValues().ToList() ?? []; - - civitBaseModels.Insert(0, CivitBaseModelType.All.ToString()); - - var filteredResults = civitBaseModels - .Where(s => s.Equals("odor", StringComparison.OrdinalIgnoreCase) == false) - .OrderBy(s => s) - .ToList(); - - return filteredResults; - } - catch (Exception e) - { - Logger.Error(e, "Failed to get base model list"); - return []; - } + return await baseModelTypeService.GetBaseModelTypes(); } public override string Header => Resources.Label_CivitAi; diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs index 360b7309..415a05a5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs @@ -59,7 +59,13 @@ public partial class CheckpointsPageViewModel( INotificationService notificationService, IMetadataImportService metadataImportService, IModelImportService modelImportService, +<<<<<<< HEAD ServiceManager dialogFactory +======= + OpenModelDbManager openModelDbManager, + ServiceManager dialogFactory, + ICivitBaseModelTypeService baseModelTypeService +>>>>>>> ef7bc395 (Merge pull request #946 from ionite34/fix-checkpoint-filters) ) : PageViewModelBase { public override string Title => Resources.Label_CheckpointManager; @@ -116,14 +122,6 @@ ServiceManager dialogFactory [ObservableProperty] private bool isDragOver; - [ObservableProperty] - private ObservableCollection baseModelOptions = - new( - Enum.GetValues() - .Where(x => x != CivitBaseModelType.All) - .Select(x => x.GetStringValue()) - ); - [ObservableProperty] private ObservableCollection selectedBaseModels = []; @@ -156,26 +154,65 @@ ServiceManager dialogFactory ? Resources.Label_OneImageSelected.Replace("images ", "") : string.Format(Resources.Label_NumImagesSelected, NumItemsSelected).Replace("images ", ""); - protected override void OnInitialLoaded() + private SourceCache BaseModelCache { get; } = new(s => s); + + public IObservableCollection BaseModelOptions { get; set; } = + new ObservableCollectionExtended(); + + protected override async Task OnInitialLoadedAsync() { if (Design.IsDesignMode) return; - base.OnInitialLoaded(); + await base.OnInitialLoadedAsync(); + + var settingsSelectedBaseModels = settingsManager.Settings.SelectedBaseModels; + + AddDisposable( + BaseModelCache + .Connect() + .DeferUntilLoaded() + .Transform( + baseModel => + new BaseModelOptionViewModel + { + ModelType = baseModel, + IsSelected = settingsSelectedBaseModels.Contains(baseModel) + } + ) + .Bind(BaseModelOptions) + .WhenPropertyChanged(p => p.IsSelected) + .ObserveOn(SynchronizationContext.Current) + .Subscribe(next => + { + if (next.Sender.IsSelected) + SelectedBaseModels.Add(next.Sender.ModelType); + else + SelectedBaseModels.Remove(next.Sender.ModelType); + + OnPropertyChanged(nameof(ClearButtonText)); + OnPropertyChanged(nameof(SelectedBaseModels)); + }) + ); - SelectedBaseModels = new ObservableCollection(BaseModelOptions); - SelectedBaseModels.CollectionChanged += (_, _) => - { - OnPropertyChanged(nameof(ClearButtonText)); - OnPropertyChanged(nameof(SelectedBaseModels)); - settingsManager.Transaction( - settings => settings.SelectedBaseModels = SelectedBaseModels.ToList() - ); - }; + var settingsTransactionObservable = this.WhenPropertyChanged(x => x.SelectedBaseModels) + .Throttle(TimeSpan.FromMilliseconds(50)) + .ObserveOn(SynchronizationContext.Current) + .Subscribe(_ => + { + settingsManager.Transaction( + settings => settings.SelectedBaseModels = SelectedBaseModels.ToList() + ); + }); + + AddDisposable(settingsTransactionObservable); + + var baseModelTypes = await baseModelTypeService.GetBaseModelTypes(includeAllOption: false); + BaseModelCache.EditDiff(baseModelTypes); // Observable predicate from SearchQuery changes var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery) - .Throttle(TimeSpan.FromMilliseconds(100))! + .Throttle(TimeSpan.FromMilliseconds(100)) .Select( _ => (Func)( @@ -228,9 +265,7 @@ or nameof(SortConnectedModelsFirst) { var comparer = new SortExpressionComparer(); if (SortConnectedModelsFirst) - { comparer = comparer.ThenByDescending(vm => vm.CheckpointFile.HasConnectedModel); - } switch (SelectedSortOption) { @@ -301,31 +336,33 @@ or nameof(SortConnectedModelsFirst) .ObserveOn(SynchronizationContext.Current) .AsObservable(); - ModelsCache - .Connect() - .DeferUntilLoaded() - .Filter(filterPredicate) - .Filter(searchPredicate) - .Transform( - x => - new CheckpointFileViewModel( - settingsManager, - modelIndexService, - notificationService, - downloadService, - dialogFactory, - logger, - x - ) - ) - .SortAndBind(Models, comparerObservable) - .WhenPropertyChanged(p => p.IsSelected) - .Throttle(TimeSpan.FromMilliseconds(50)) - .ObserveOn(SynchronizationContext.Current) - .Subscribe(_ => - { - NumItemsSelected = Models.Count(o => o.IsSelected); - }); + AddDisposable( + ModelsCache + .Connect() + .DeferUntilLoaded() + .Filter(filterPredicate) + .Filter(searchPredicate) + .Transform( + x => + new CheckpointFileViewModel( + settingsManager, + modelIndexService, + notificationService, + downloadService, + dialogFactory, + logger, + x + ) + ) + .SortAndBind(Models, comparerObservable) + .WhenPropertyChanged(p => p.IsSelected) + .Throttle(TimeSpan.FromMilliseconds(50)) + .ObserveOn(SynchronizationContext.Current) + .Subscribe(_ => + { + NumItemsSelected = Models.Count(o => o.IsSelected); + }) + ); var categoryFilterPredicate = Observable .FromEventPattern(this, nameof(PropertyChanged)) @@ -336,31 +373,37 @@ or nameof(SortConnectedModelsFirst) .ObserveOn(SynchronizationContext.Current) .AsObservable(); - categoriesCache - .Connect() - .DeferUntilLoaded() - .Filter(categoryFilterPredicate) - .SortAndBind( - Categories, - SortExpressionComparer - .Descending(x => x.Name == "All Models") - .ThenByAscending(x => x.Name) - ) - .ObserveOn(SynchronizationContext.Current) - .Subscribe(); + AddDisposable( + categoriesCache + .Connect() + .DeferUntilLoaded() + .Filter(categoryFilterPredicate) + .SortAndBind( + Categories, + SortExpressionComparer + .Descending(x => x.Name == "All Models") + .ThenByAscending(x => x.Name) + ) + .ObserveOn(SynchronizationContext.Current) + .Subscribe() + ); - settingsManager.RelayPropertyFor( - this, - vm => vm.IsImportAsConnectedEnabled, - s => s.IsImportAsConnected, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.IsImportAsConnectedEnabled, + s => s.IsImportAsConnected, + true + ) ); - settingsManager.RelayPropertyFor( - this, - vm => vm.ResizeFactor, - s => s.CheckpointsPageResizeFactor, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.ResizeFactor, + s => s.CheckpointsPageResizeFactor, + true + ) ); Refresh().SafeFireAndForget(); @@ -374,53 +417,67 @@ or nameof(SortConnectedModelsFirst) ); }; - settingsManager.RelayPropertyFor( - this, - vm => vm.SortConnectedModelsFirst, - settings => settings.SortConnectedModelsFirst, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.SortConnectedModelsFirst, + settings => settings.SortConnectedModelsFirst, + true + ) ); - settingsManager.RelayPropertyFor( - this, - vm => vm.SelectedSortOption, - settings => settings.CheckpointSortMode, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.SelectedSortOption, + settings => settings.CheckpointSortMode, + true + ) ); - settingsManager.RelayPropertyFor( - this, - vm => vm.SelectedSortDirection, - settings => settings.CheckpointSortDirection, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.SelectedSortDirection, + settings => settings.CheckpointSortDirection, + true + ) ); - settingsManager.RelayPropertyFor( - this, - vm => vm.ShowModelsInSubfolders, - settings => settings.ShowModelsInSubfolders, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.ShowModelsInSubfolders, + settings => settings.ShowModelsInSubfolders, + true + ) ); - settingsManager.RelayPropertyFor( - this, - vm => vm.DragMovesAllSelected, - settings => settings.DragMovesAllSelected, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.DragMovesAllSelected, + settings => settings.DragMovesAllSelected, + true + ) ); - settingsManager.RelayPropertyFor( - this, - vm => vm.HideEmptyRootCategories, - settings => settings.HideEmptyRootCategories, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.HideEmptyRootCategories, + settings => settings.HideEmptyRootCategories, + true + ) ); - settingsManager.RelayPropertyFor( - this, - vm => vm.ShowNsfwImages, - settings => settings.ShowNsfwInCheckpointsPage, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.ShowNsfwImages, + settings => settings.ShowNsfwInCheckpointsPage, + true + ) ); // make sure a sort happens @@ -444,9 +501,7 @@ private void ClearSelection() { var selected = Models.Where(x => x.IsSelected).ToList(); foreach (var model in selected) - { model.IsSelected = false; - } NumItemsSelected = 0; } @@ -459,9 +514,7 @@ private async Task DeleteAsync() || Models.Where(o => o.IsSelected).Select(vm => vm.CheckpointFile).ToList() is not { Count: > 0 } selectedModelFiles ) - { return; - } var pathsToDelete = selectedModelFiles .SelectMany(x => x.GetDeleteFullPaths(settingsManager.ModelsDirectory)) @@ -471,9 +524,7 @@ private async Task DeleteAsync() confirmDeleteVm.PathsToDelete = pathsToDelete; if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary) - { return; - } try { @@ -534,17 +585,11 @@ private Task OnItemClick(CheckpointFileViewModel item) { // Select item if we're in "select mode" if (NumItemsSelected > 0) - { item.IsSelected = !item.IsSelected; - } else if (item.CheckpointFile.HasConnectedModel) - { return ShowVersionDialog(item); - } else - { item.IsSelected = !item.IsSelected; - } return Task.CompletedTask; } @@ -596,7 +641,7 @@ private async Task ShowVersionDialog(CheckpointFileViewModel item) IsFooterVisible = false, CloseOnClickOutside = true, MaxDialogWidth = 750, - MaxDialogHeight = 1000, + MaxDialogHeight = 1000 }; var htmlDescription = $"""{model.Description}"""; @@ -649,14 +694,9 @@ private async Task ShowVersionDialog(CheckpointFileViewModel item) private void ClearOrSelectAllBaseModels() { if (SelectedBaseModels.Count == BaseModelOptions.Count) - { - SelectedBaseModels.Clear(); - } + BaseModelOptions.ForEach(x => x.IsSelected = false); else - { - SelectedBaseModels.Clear(); - SelectedBaseModels.AddRange(BaseModelOptions); - } + BaseModelOptions.ForEach(x => x.IsSelected = true); } [RelayCommand] @@ -701,10 +741,12 @@ await notificationService.TryAsync( } [RelayCommand] - private Task OpenFolderFromTreeview(object? treeViewItem) => - treeViewItem is CheckpointCategory category && !string.IsNullOrWhiteSpace(category.Path) + private Task OpenFolderFromTreeview(object? treeViewItem) + { + return treeViewItem is CheckpointCategory category && !string.IsNullOrWhiteSpace(category.Path) ? ProcessRunner.OpenFolderBrowser(category.Path) : Task.CompletedTask; + } [RelayCommand] private async Task DeleteFolderAsync(object? treeViewItem) @@ -826,19 +868,13 @@ public async Task MoveBetweenFolders(LocalModelFile sourceFile, DirectoryPath de // Move files if (File.Exists(sourcePath)) - { await FileTransfers.MoveFileAsync(sourcePath, destinationFilePath); - } if (File.Exists(sourceCmInfoPath)) - { await FileTransfers.MoveFileAsync(sourceCmInfoPath, destinationCmInfoPath); - } if (File.Exists(sourcePreviewPath)) - { await FileTransfers.MoveFileAsync(sourcePreviewPath, destinationPreviewPath); - } notificationService.Show( "Model moved successfully", @@ -892,7 +928,6 @@ private void RefreshCategories() .ToList(); foreach (var checkpointCategory in modelCategories.SelectMany(c => c.Flatten())) - { checkpointCategory.Count = Directory .EnumerateFileSystemEntries( checkpointCategory.Path, @@ -900,13 +935,12 @@ private void RefreshCategories() EnumerationOptionConstants.AllDirectories ) .Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(x))); - } var rootCategory = new CheckpointCategory { Path = settingsManager.ModelsDirectory, Name = "All Models", - Count = modelIndexService.ModelIndex.Values.SelectMany(x => x).Count(), + Count = modelIndexService.ModelIndex.Values.SelectMany(x => x).Count() }; categoriesCache.Edit(updater => @@ -928,9 +962,7 @@ private void RefreshCategories() .SelectMany(x => x.Flatten()) .FirstOrDefault(x => x.Path == dirPath.FullPath); if (category != null) - { category.IsExpanded = true; - } dirPath = dirPath.Parent; } @@ -966,13 +998,11 @@ private ObservableCollection GetSubfolders(string strPath) Path = dir, Count = new DirectoryInfo(dir) .EnumerateFileSystemInfos("*", EnumerationOptionConstants.AllDirectories) - .Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(x.Extension)), + .Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(x.Extension)) }; if (Directory.GetDirectories(dir, "*", EnumerationOptionConstants.TopLevelOnly).Length > 0) - { category.SubDirectories = GetSubfolders(dir); - } subfolders.Add(category); } @@ -983,11 +1013,9 @@ private ObservableCollection GetSubfolders(string strPath) private string GetConnectedModelInfoFilePath(string filePath) { if (string.IsNullOrEmpty(filePath)) - { throw new InvalidOperationException( "Cannot get connected model info file path when filePath is empty" ); - } var modelNameNoExt = Path.GetFileNameWithoutExtension((string?)filePath); var modelDir = Path.GetDirectoryName((string?)filePath) ?? ""; @@ -1017,11 +1045,9 @@ private void DelayedClearViewModelProgress(CheckpointFileViewModel viewModel, Ti private bool FilterModels(LocalModelFile file) { if (SelectedCategory?.Path is null || SelectedCategory?.Path == settingsManager.ModelsDirectory) - { return file.HasConnectedModel ? SelectedBaseModels.Contains(file.ConnectedModelInfo.BaseModel ?? "Other") : SelectedBaseModels.Contains("Other"); - } var folderPath = Path.GetDirectoryName(file.RelativePath); var categoryRelativePath = SelectedCategory @@ -1030,9 +1056,7 @@ private bool FilterModels(LocalModelFile file) .TrimStart(Path.DirectorySeparatorChar); if (categoryRelativePath == null || folderPath == null) - { return false; - } if ( ( @@ -1042,9 +1066,7 @@ private bool FilterModels(LocalModelFile file) ) is false ) - { return false; - } return ShowModelsInSubfolders ? folderPath.StartsWith(categoryRelativePath) diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml index 709ffaf3..82b60b94 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml @@ -34,6 +34,7 @@ False True + @@ -288,28 +289,27 @@ - +