From 6657cf59ea60585724ec607df7b34cb5297ae0c1 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 16 Mar 2024 02:36:12 -0700 Subject: [PATCH 1/2] Converted civitai browser to new pagination cursor stuff & converted backend to use DynamicData --- CHANGELOG.md | 6 + .../DesignData/DesignData.cs | 2 +- .../Models/IInfinitelyScroll.cs | 8 + .../CivitAiBrowserViewModel.cs | 216 +++++++----------- .../Views/CivitAiBrowserPage.axaml | 68 ++---- .../Views/CivitAiBrowserPage.axaml.cs | 16 +- .../Models/Api/CivitMetadata.cs | 14 +- .../Models/Api/CivitModelStats.cs | 5 +- .../Models/Api/CivitModelsRequest.cs | 60 ++--- 9 files changed, 168 insertions(+), 227 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a543042..c5e42023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.10.0-dev.3 ### Added - Added support for deep links from the new Stability Matrix Chrome extension +### Changed +- Due to changes on the CivitAI API, you can no longer select a specific page in the CivitAI Model Browser +- Due to the above API changes, new pages are now loaded via "infinite scrolling" ### Fixed - Fixed Inference HiresFix module "Inherit Primary Sampler Addons" setting not effectively disabling when unchecked - Fixed model download location options for VAEs in the CivitAI Model Browser @@ -16,6 +19,9 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Fixed ComfyUI with Inference pop-up during one-click install appearing below the visible scroll area - Fixed no packages being available for one-click install on PCs without a GPU - Fixed models not being removed from the installed models cache when deleting them from the Checkpoints page +- Fixed missing ratings on some models in the CivitAI Model Browser +- Fixed missing favorite count in the CivitAI Model Browser +- Fixed recommended models not showing all SDXL models ## v2.10.0-dev.2 ### Added diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index f6d7d011..df122b14 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -325,7 +325,7 @@ public static class DesignData ); }*/ - CivitAiBrowserViewModel.ModelCards = new ObservableCollection + CivitAiBrowserViewModel.ModelCards = new ObservableCollectionExtended { dialogFactory.Get(vm => { diff --git a/StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs b/StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs new file mode 100644 index 00000000..59373816 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace StabilityMatrix.Avalonia.Models; + +public interface IInfinitelyScroll +{ + Task LoadNextPageAsync(); +} diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs index 924d17ed..3b755432 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Net.Http; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading; using System.Threading.Tasks; using AsyncAwaitBestPractices; using Avalonia.Collections; @@ -15,12 +11,15 @@ using Avalonia.Controls; using Avalonia.Controls.Notifications; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using DynamicData; +using DynamicData.Alias; +using DynamicData.Binding; using LiteDB; using LiteDB.Async; using NLog; -using OneOf.Types; using Refit; using StabilityMatrix.Avalonia.Languages; +using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; @@ -41,24 +40,26 @@ namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; [View(typeof(CivitAiBrowserPage))] [Singleton] -public partial class CivitAiBrowserViewModel : TabViewModelBase +public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScroll { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly ICivitApi civitApi; - private readonly IDownloadService downloadService; private readonly ISettingsManager settingsManager; private readonly ServiceManager dialogFactory; private readonly ILiteDbContext liteDbContext; private readonly INotificationService notificationService; private const int MaxModelsPerPage = 20; + private LRUCache< int /* model id */ , CheckpointBrowserCardViewModel > cache = new(50); - [ObservableProperty] - private ObservableCollection? modelCards; + public SourceCache ModelCache { get; } = new(m => m.Id); + + public IObservableCollection ModelCards { get; set; } = + new ObservableCollectionExtended(); [ObservableProperty] private DataGridCollectionView? modelCardsView; @@ -81,27 +82,9 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase [ObservableProperty] private CivitModelType selectedModelType = CivitModelType.Checkpoint; - [ObservableProperty] - private int currentPageNumber; - - [ObservableProperty] - private int totalPages; - [ObservableProperty] private bool hasSearched; - [ObservableProperty] - private bool canGoToNextPage; - - [ObservableProperty] - private bool canGoToPreviousPage; - - [ObservableProperty] - private bool canGoToFirstPage; - - [ObservableProperty] - private bool canGoToLastPage; - [ObservableProperty] private bool isIndeterminate; @@ -117,7 +100,8 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase [ObservableProperty] private bool showSantaHats = true; - private List allModelCards = new(); + [ObservableProperty] + private string? nextPageCursor; public IEnumerable AllCivitPeriods => Enum.GetValues(typeof(CivitPeriod)).Cast(); @@ -143,24 +127,45 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase ) { this.civitApi = civitApi; - this.downloadService = downloadService; this.settingsManager = settingsManager; this.dialogFactory = dialogFactory; this.liteDbContext = liteDbContext; this.notificationService = notificationService; - CurrentPageNumber = 1; - CanGoToNextPage = true; - CanGoToLastPage = true; + ModelCache + .Connect() + .DeferUntilLoaded() + .Select(model => + { + var cachedViewModel = cache.Get(model.Id); + if (cachedViewModel != null) + { + if (!cachedViewModel.IsImporting) + { + cache.Remove(model.Id); + } + + return cachedViewModel; + } + + var newCard = dialogFactory.Get(vm => + { + vm.CivitModel = model; + vm.OnDownloadStart = viewModel => + { + if (cache.Get(viewModel.CivitModel.Id) != null) + return; + cache.Add(viewModel.CivitModel.Id, viewModel); + }; + + return vm; + }); - Observable - .FromEventPattern(this, nameof(PropertyChanged)) - .Where(x => x.EventArgs.PropertyName == nameof(CurrentPageNumber)) - .Throttle(TimeSpan.FromMilliseconds(250)) - .Select, int>(_ => CurrentPageNumber) - .Where(page => page <= TotalPages && page > 0) - .ObserveOn(SynchronizationContext.Current) - .Subscribe(_ => TrySearchAgain(false).SafeFireAndForget(), err => Logger.Error(err)); + return newCard; + }) + .Filter(FilterModelCardsPredicate) + .Bind(ModelCards) + .Subscribe(); EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested; } @@ -171,7 +176,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase return; SearchQuery = $"$#{e}"; - SearchModelsCommand.ExecuteAsync(null).SafeFireAndForget(); + SearchModelsCommand.ExecuteAsync(false).SafeFireAndForget(); } public override void OnLoaded() @@ -223,7 +228,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase /// /// Background update task /// - private async Task CivitModelQuery(CivitModelsRequest request) + private async Task CivitModelQuery(CivitModelsRequest request, bool isInfiniteScroll = false) { var timer = Stopwatch.StartNew(); var queryText = request.Query; @@ -279,12 +284,14 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase if (cacheNew) { Logger.Debug("New cache entry, updating model cards"); - UpdateModelCards(models, modelsResponse.Metadata); + UpdateModelCards(models, isInfiniteScroll); } else { Logger.Debug("Cache entry already exists, not updating model cards"); } + + NextPageCursor = modelsResponse.Metadata?.NextCursor; } catch (OperationCanceledException) { @@ -327,60 +334,22 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase /// /// Updates model cards using api response object. /// - private void UpdateModelCards(IEnumerable? models, CivitMetadata? metadata) + private void UpdateModelCards(List? models, bool addCards = false) { if (models is null) { - ModelCards?.Clear(); + ModelCache?.Clear(); } else { - var updateCards = models - .Select(model => - { - var cachedViewModel = cache.Get(model.Id); - if (cachedViewModel != null) - { - if (!cachedViewModel.IsImporting) - { - cache.Remove(model.Id); - } - - return cachedViewModel; - } - - var newCard = dialogFactory.Get(vm => - { - vm.CivitModel = model; - vm.OnDownloadStart = viewModel => - { - if (cache.Get(viewModel.CivitModel.Id) != null) - return; - cache.Add(viewModel.CivitModel.Id, viewModel); - }; - - return vm; - }); - - return newCard; - }) - .ToList(); - - allModelCards = updateCards; - - var filteredCards = updateCards.Where(FilterModelCardsPredicate); - if (SortMode == CivitSortMode.Installed) + if (!addCards) { - filteredCards = filteredCards.OrderByDescending(x => x.UpdateCardText == "Update Available"); + ModelCache.Clear(); } - ModelCards = new ObservableCollection(filteredCards); + ModelCache.AddOrUpdate(models); } - TotalPages = metadata?.TotalPages ?? 1; - CanGoToFirstPage = CurrentPageNumber != 1; - CanGoToPreviousPage = CurrentPageNumber > 1; - CanGoToNextPage = CurrentPageNumber < TotalPages; - CanGoToLastPage = CurrentPageNumber != TotalPages; + // Status update ShowMainLoadingSpinner = false; IsIndeterminate = false; @@ -390,15 +359,15 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase private string previousSearchQuery = string.Empty; [RelayCommand] - private async Task SearchModels() + private async Task SearchModels(bool isInfiniteScroll = false) { var timer = Stopwatch.StartNew(); - if (SearchQuery != previousSearchQuery) + if (SearchQuery != previousSearchQuery || !isInfiniteScroll) { // Reset page number - CurrentPageNumber = 1; previousSearchQuery = SearchQuery; + NextPageCursor = null; } // Build request @@ -407,10 +376,14 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase Limit = MaxModelsPerPage, Nsfw = "true", // Handled by local view filter Sort = SortMode, - Period = SelectedPeriod, - Page = CurrentPageNumber + Period = SelectedPeriod }; + if (NextPageCursor != null) + { + modelRequest.Cursor = NextPageCursor; + } + if (SelectedModelType != CivitModelType.All) { modelRequest.Types = [SelectedModelType]; @@ -516,14 +489,15 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase modelRequest.GetHashCode(), elapsed.TotalSeconds ); - UpdateModelCards(cachedQuery.Items, cachedQuery.Metadata); + NextPageCursor = cachedQuery.Metadata?.NextCursor; + UpdateModelCards(cachedQuery.Items, isInfiniteScroll); // Start remote query (background mode) // Skip when last query was less than 2 min ago var timeSinceCache = DateTimeOffset.UtcNow - cachedQuery.InsertedAt; if (timeSinceCache?.TotalMinutes >= 2) { - CivitModelQuery(modelRequest).SafeFireAndForget(); + CivitModelQuery(modelRequest, isInfiniteScroll).SafeFireAndForget(); Logger.Debug( "Cached query was more than 2 minutes ago ({Seconds:F0} s), updating cache with remote query", timeSinceCache.Value.TotalSeconds @@ -534,54 +508,23 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase { // Not cached, wait for remote query ShowMainLoadingSpinner = true; - await CivitModelQuery(modelRequest); + await CivitModelQuery(modelRequest, isInfiniteScroll); } UpdateResultsText(); } - public void FirstPage() - { - CurrentPageNumber = 1; - } - - public void PreviousPage() - { - if (CurrentPageNumber == 1) - return; - - CurrentPageNumber--; - } - - public void NextPage() - { - if (CurrentPageNumber == TotalPages) - return; - - CurrentPageNumber++; - } - - public void LastPage() - { - CurrentPageNumber = TotalPages; - } - public void ClearSearchQuery() { SearchQuery = string.Empty; } - partial void OnShowNsfwChanged(bool value) + public async Task LoadNextPageAsync() { - settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled, value); - // ModelCardsView?.Refresh(); - var updateCards = allModelCards.Where(FilterModelCardsPredicate); - ModelCards = new ObservableCollection(updateCards); - - if (!HasSearched) - return; - - UpdateResultsText(); + if (NextPageCursor != null) + { + await SearchModelsCommand.ExecuteAsync(true); + } } partial void OnSelectedPeriodChanged(CivitPeriod value) @@ -596,6 +539,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase SelectedBaseModelType ) ); + NextPageCursor = null; } partial void OnSortModeChanged(CivitSortMode value) @@ -610,6 +554,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase SelectedBaseModelType ) ); + NextPageCursor = null; } partial void OnSelectedModelTypeChanged(CivitModelType value) @@ -624,6 +569,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase SelectedBaseModelType ) ); + NextPageCursor = null; } partial void OnSelectedBaseModelTypeChanged(string value) @@ -638,6 +584,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase value ) ); + NextPageCursor = null; } private async Task TrySearchAgain(bool shouldUpdatePageNumber = true) @@ -648,18 +595,17 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase if (shouldUpdatePageNumber) { - CurrentPageNumber = 1; + NextPageCursor = null; } // execute command instead of calling method directly so that the IsRunning property gets updated - await SearchModelsCommand.ExecuteAsync(null); + await SearchModelsCommand.ExecuteAsync(false); } private void UpdateResultsText() { NoResultsFound = ModelCards?.Count <= 0; - NoResultsText = - allModelCards.Count > 0 ? $"{allModelCards.Count} results hidden by filters" : "No results found"; + NoResultsText = "No results found"; } public override string Header => Resources.Label_CivitAi; diff --git a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml index ba065242..ed5643a4 100644 --- a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml @@ -16,6 +16,7 @@ xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" xmlns:controls1="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls" + xmlns:system="clr-namespace:System;assembly=System.Runtime" d:DataContext="{x:Static designData:DesignData.CivitAiBrowserViewModel}" d:DesignHeight="700" d:DesignWidth="800" @@ -71,6 +72,7 @@ + False + Text="{Binding CivitModel.Stats.ThumbsUpCount, Converter={StaticResource KiloFormatterConverter}}" /> - - - - - - - - - - - - - - - + + + + + + + + + @@ -10,99 +9,99 @@ public class CivitModelsRequest /// [AliasAs("limit")] public int? Limit { get; set; } - + /// /// The page from which to start fetching models /// [AliasAs("page")] public int? Page { get; set; } - + /// /// Search query to filter models by name /// [AliasAs("query")] public string? Query { get; set; } - + /// /// Search query to filter models by tag /// [AliasAs("tag")] public string? Tag { get; set; } - + /// /// Search query to filter models by user /// [AliasAs("username")] public string? Username { get; set; } - + /// /// The type of model you want to filter with. If none is specified, it will return all types /// [AliasAs("types")] public CivitModelType[]? Types { get; set; } - + /// /// The order in which you wish to sort the results /// [AliasAs("sort")] public CivitSortMode? Sort { get; set; } - + /// /// The time frame in which the models will be sorted /// [AliasAs("period")] public CivitPeriod? Period { get; set; } - + /// /// The rating you wish to filter the models with. If none is specified, it will return models with any rating /// [AliasAs("rating")] public int? Rating { get; set; } - + /// /// Filter to models that require or don't require crediting the creator /// Requires Authentication /// [AliasAs("favorites")] public bool? Favorites { get; set; } - + /// /// Filter to hidden models of the authenticated user /// Requires Authentication /// [AliasAs("hidden")] public bool? Hidden { get; set; } - + /// /// Only include the primary file for each model (This will use your preferred format options if you use an API token or session cookie) /// [AliasAs("primaryFileOnly")] public bool? PrimaryFileOnly { get; set; } - + /// /// Filter to models that allow or don't allow creating derivatives /// [AliasAs("allowDerivatives")] public bool? AllowDerivatives { get; set; } - + /// /// Filter to models that allow or don't allow derivatives to have a different license /// [AliasAs("allowDifferentLicenses")] public bool? AllowDifferentLicenses { get; set; } - + /// /// Filter to models based on their commercial permissions /// [AliasAs("allowCommercialUse")] public CivitCommercialUse? AllowCommercialUse { get; set; } - + /// /// If false, will return safer images and hide models that don't have safe images /// [AliasAs("nsfw")] public string? Nsfw { get; set; } - + /// /// options:
/// SD 1.4
@@ -117,22 +116,25 @@ public class CivitModelsRequest ///
[AliasAs("baseModels")] public string? BaseModel { get; set; } - + [AliasAs("ids")] public string CommaSeparatedModelIds { get; set; } + [AliasAs("cursor")] + public string? Cursor { get; set; } + public override string ToString() { - return $"Page: {Page}, " + - $"Query: {Query}, " + - $"Tag: {Tag}, " + - $"Username: {Username}, " + - $"Types: {Types}, " + - $"Sort: {Sort}, " + - $"Period: {Period}, " + - $"Rating: {Rating}, " + - $"Nsfw: {Nsfw}, " + - $"BaseModel: {BaseModel}, " + - $"CommaSeparatedModelIds: {CommaSeparatedModelIds}"; + return $"Page: {Page}, " + + $"Query: {Query}, " + + $"Tag: {Tag}, " + + $"Username: {Username}, " + + $"Types: {Types}, " + + $"Sort: {Sort}, " + + $"Period: {Period}, " + + $"Rating: {Rating}, " + + $"Nsfw: {Nsfw}, " + + $"BaseModel: {BaseModel}, " + + $"CommaSeparatedModelIds: {CommaSeparatedModelIds}"; } } From 19cd60c1951dcd30a9d424e861dd4911765aa1e4 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 16 Mar 2024 17:28:04 -0700 Subject: [PATCH 2/2] Fixed all that I can fix --- .../CivitAiBrowserViewModel.cs | 110 +++++++++--------- .../Views/CivitAiBrowserPage.axaml | 2 +- .../Models/Api/CivitModelVersion.cs | 21 ++-- 3 files changed, 70 insertions(+), 63 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs index 3b755432..4754397d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Net.Http; +using System.Reactive.Linq; using System.Threading.Tasks; using AsyncAwaitBestPractices; using Avalonia.Collections; @@ -54,12 +55,10 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro int /* model id */ , CheckpointBrowserCardViewModel - > cache = new(50); + > cache = new(150); - public SourceCache ModelCache { get; } = new(m => m.Id); - - public IObservableCollection ModelCards { get; set; } = - new ObservableCollectionExtended(); + [ObservableProperty] + private ObservableCollection modelCards = new(); [ObservableProperty] private DataGridCollectionView? modelCardsView; @@ -132,41 +131,6 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro this.liteDbContext = liteDbContext; this.notificationService = notificationService; - ModelCache - .Connect() - .DeferUntilLoaded() - .Select(model => - { - var cachedViewModel = cache.Get(model.Id); - if (cachedViewModel != null) - { - if (!cachedViewModel.IsImporting) - { - cache.Remove(model.Id); - } - - return cachedViewModel; - } - - var newCard = dialogFactory.Get(vm => - { - vm.CivitModel = model; - vm.OnDownloadStart = viewModel => - { - if (cache.Get(viewModel.CivitModel.Id) != null) - return; - cache.Add(viewModel.CivitModel.Id, viewModel); - }; - - return vm; - }); - - return newCard; - }) - .Filter(FilterModelCardsPredicate) - .Bind(ModelCards) - .Subscribe(); - EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested; } @@ -281,15 +245,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro } ); - if (cacheNew) - { - Logger.Debug("New cache entry, updating model cards"); - UpdateModelCards(models, isInfiniteScroll); - } - else - { - Logger.Debug("Cache entry already exists, not updating model cards"); - } + UpdateModelCards(models, isInfiniteScroll); NextPageCursor = modelsResponse.Metadata?.NextCursor; } @@ -338,16 +294,65 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro { if (models is null) { - ModelCache?.Clear(); + ModelCards?.Clear(); } else { + var modelsToAdd = models + .Select(model => + { + var cachedViewModel = cache.Get(model.Id); + if (cachedViewModel != null) + { + if (!cachedViewModel.IsImporting) + { + cache.Remove(model.Id); + } + + return cachedViewModel; + } + + var newCard = dialogFactory.Get(vm => + { + vm.CivitModel = model; + vm.OnDownloadStart = viewModel => + { + if (cache.Get(viewModel.CivitModel.Id) != null) + return; + cache.Add(viewModel.CivitModel.Id, viewModel); + }; + + return vm; + }); + + return newCard; + }) + .Where(FilterModelCardsPredicate); + + if (SortMode == CivitSortMode.Installed) + { + modelsToAdd = modelsToAdd.OrderByDescending(x => x.UpdateCardText == "Update Available"); + } + if (!addCards) { - ModelCache.Clear(); + ModelCards = new ObservableCollection(modelsToAdd); } + else + { + foreach (var model in modelsToAdd) + { + if ( + ModelCards.Contains( + model, + new PropertyComparer(x => x.CivitModel.Id) + ) + ) + continue; - ModelCache.AddOrUpdate(models); + ModelCards.Add(model); + } + } } // Status update @@ -373,7 +378,6 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScro // Build request var modelRequest = new CivitModelsRequest { - Limit = MaxModelsPerPage, Nsfw = "true", // Handled by local view filter Sort = SortMode, Period = SelectedPeriod diff --git a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml index ed5643a4..25755a05 100644 --- a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml @@ -109,7 +109,7 @@ HorizontalAlignment="Center" Height="75" ZIndex="10" - IsVisible="{Binding ShowSantaHats}" + IsVisible="{Binding ShowSantaHats, FallbackValue=False}" Margin="0,8,0,0" Source="avares://StabilityMatrix.Avalonia/Assets/santahat.png"> diff --git a/StabilityMatrix.Core/Models/Api/CivitModelVersion.cs b/StabilityMatrix.Core/Models/Api/CivitModelVersion.cs index e1ae02cc..75edd906 100644 --- a/StabilityMatrix.Core/Models/Api/CivitModelVersion.cs +++ b/StabilityMatrix.Core/Models/Api/CivitModelVersion.cs @@ -6,31 +6,34 @@ public class CivitModelVersion { [JsonPropertyName("id")] public int Id { get; set; } - + [JsonPropertyName("name")] public string Name { get; set; } - + [JsonPropertyName("description")] public string Description { get; set; } - + [JsonPropertyName("createdAt")] public DateTime CreatedAt { get; set; } - + [JsonPropertyName("downloadUrl")] public string DownloadUrl { get; set; } - + [JsonPropertyName("trainedWords")] public string[] TrainedWords { get; set; } - + [JsonPropertyName("baseModel")] public string? BaseModel { get; set; } - + [JsonPropertyName("files")] public List? Files { get; set; } - + [JsonPropertyName("images")] public List? Images { get; set; } - + [JsonPropertyName("stats")] public CivitModelStats Stats { get; set; } + + [JsonPropertyName("publishedAt")] + public DateTimeOffset? PublishedAt { get; set; } }