|
|
|
@ -1,13 +1,10 @@
|
|
|
|
|
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 +12,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 +41,24 @@ 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<ViewModelBase> dialogFactory; |
|
|
|
|
private readonly ILiteDbContext liteDbContext; |
|
|
|
|
private readonly INotificationService notificationService; |
|
|
|
|
private const int MaxModelsPerPage = 20; |
|
|
|
|
|
|
|
|
|
private LRUCache< |
|
|
|
|
int /* model id */ |
|
|
|
|
, |
|
|
|
|
CheckpointBrowserCardViewModel |
|
|
|
|
> cache = new(50); |
|
|
|
|
> cache = new(150); |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private ObservableCollection<CheckpointBrowserCardViewModel>? modelCards; |
|
|
|
|
private ObservableCollection<CheckpointBrowserCardViewModel> modelCards = new(); |
|
|
|
|
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private DataGridCollectionView? modelCardsView; |
|
|
|
@ -81,27 +81,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 +99,8 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
[ObservableProperty] |
|
|
|
|
private bool showSantaHats = true; |
|
|
|
|
|
|
|
|
|
private List<CheckpointBrowserCardViewModel> allModelCards = new(); |
|
|
|
|
[ObservableProperty] |
|
|
|
|
private string? nextPageCursor; |
|
|
|
|
|
|
|
|
|
public IEnumerable<CivitPeriod> AllCivitPeriods => |
|
|
|
|
Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>(); |
|
|
|
@ -143,25 +126,11 @@ 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; |
|
|
|
|
|
|
|
|
|
Observable |
|
|
|
|
.FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged)) |
|
|
|
|
.Where(x => x.EventArgs.PropertyName == nameof(CurrentPageNumber)) |
|
|
|
|
.Throttle(TimeSpan.FromMilliseconds(250)) |
|
|
|
|
.Select<EventPattern<PropertyChangedEventArgs>, int>(_ => CurrentPageNumber) |
|
|
|
|
.Where(page => page <= TotalPages && page > 0) |
|
|
|
|
.ObserveOn(SynchronizationContext.Current) |
|
|
|
|
.Subscribe(_ => TrySearchAgain(false).SafeFireAndForget(), err => Logger.Error(err)); |
|
|
|
|
|
|
|
|
|
EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -171,7 +140,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
SearchQuery = $"$#{e}"; |
|
|
|
|
SearchModelsCommand.ExecuteAsync(null).SafeFireAndForget(); |
|
|
|
|
SearchModelsCommand.ExecuteAsync(false).SafeFireAndForget(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override void OnLoaded() |
|
|
|
@ -223,7 +192,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Background update task |
|
|
|
|
/// </summary> |
|
|
|
|
private async Task CivitModelQuery(CivitModelsRequest request) |
|
|
|
|
private async Task CivitModelQuery(CivitModelsRequest request, bool isInfiniteScroll = false) |
|
|
|
|
{ |
|
|
|
|
var timer = Stopwatch.StartNew(); |
|
|
|
|
var queryText = request.Query; |
|
|
|
@ -276,15 +245,9 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (cacheNew) |
|
|
|
|
{ |
|
|
|
|
Logger.Debug("New cache entry, updating model cards"); |
|
|
|
|
UpdateModelCards(models, modelsResponse.Metadata); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
Logger.Debug("Cache entry already exists, not updating model cards"); |
|
|
|
|
} |
|
|
|
|
UpdateModelCards(models, isInfiniteScroll); |
|
|
|
|
|
|
|
|
|
NextPageCursor = modelsResponse.Metadata?.NextCursor; |
|
|
|
|
} |
|
|
|
|
catch (OperationCanceledException) |
|
|
|
|
{ |
|
|
|
@ -327,7 +290,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Updates model cards using api response object. |
|
|
|
|
/// </summary> |
|
|
|
|
private void UpdateModelCards(IEnumerable<CivitModel>? models, CivitMetadata? metadata) |
|
|
|
|
private void UpdateModelCards(List<CivitModel>? models, bool addCards = false) |
|
|
|
|
{ |
|
|
|
|
if (models is null) |
|
|
|
|
{ |
|
|
|
@ -335,7 +298,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
var updateCards = models |
|
|
|
|
var modelsToAdd = models |
|
|
|
|
.Select(model => |
|
|
|
|
{ |
|
|
|
|
var cachedViewModel = cache.Get(model.Id); |
|
|
|
@ -364,23 +327,34 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
|
|
|
|
|
return newCard; |
|
|
|
|
}) |
|
|
|
|
.ToList(); |
|
|
|
|
.Where(FilterModelCardsPredicate); |
|
|
|
|
|
|
|
|
|
allModelCards = updateCards; |
|
|
|
|
|
|
|
|
|
var filteredCards = updateCards.Where(FilterModelCardsPredicate); |
|
|
|
|
if (SortMode == CivitSortMode.Installed) |
|
|
|
|
{ |
|
|
|
|
filteredCards = filteredCards.OrderByDescending(x => x.UpdateCardText == "Update Available"); |
|
|
|
|
modelsToAdd = modelsToAdd.OrderByDescending(x => x.UpdateCardText == "Update Available"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!addCards) |
|
|
|
|
{ |
|
|
|
|
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(modelsToAdd); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
foreach (var model in modelsToAdd) |
|
|
|
|
{ |
|
|
|
|
if ( |
|
|
|
|
ModelCards.Contains( |
|
|
|
|
model, |
|
|
|
|
new PropertyComparer<CheckpointBrowserCardViewModel>(x => x.CivitModel.Id) |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(filteredCards); |
|
|
|
|
ModelCards.Add(model); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
TotalPages = metadata?.TotalPages ?? 1; |
|
|
|
|
CanGoToFirstPage = CurrentPageNumber != 1; |
|
|
|
|
CanGoToPreviousPage = CurrentPageNumber > 1; |
|
|
|
|
CanGoToNextPage = CurrentPageNumber < TotalPages; |
|
|
|
|
CanGoToLastPage = CurrentPageNumber != TotalPages; |
|
|
|
|
|
|
|
|
|
// Status update |
|
|
|
|
ShowMainLoadingSpinner = false; |
|
|
|
|
IsIndeterminate = false; |
|
|
|
@ -390,27 +364,30 @@ 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 |
|
|
|
|
var modelRequest = new CivitModelsRequest |
|
|
|
|
{ |
|
|
|
|
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 +493,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 +512,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<CheckpointBrowserCardViewModel>(updateCards); |
|
|
|
|
|
|
|
|
|
if (!HasSearched) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
UpdateResultsText(); |
|
|
|
|
if (NextPageCursor != null) |
|
|
|
|
{ |
|
|
|
|
await SearchModelsCommand.ExecuteAsync(true); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
partial void OnSelectedPeriodChanged(CivitPeriod value) |
|
|
|
@ -596,6 +543,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
SelectedBaseModelType |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
NextPageCursor = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
partial void OnSortModeChanged(CivitSortMode value) |
|
|
|
@ -610,6 +558,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
SelectedBaseModelType |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
NextPageCursor = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
partial void OnSelectedModelTypeChanged(CivitModelType value) |
|
|
|
@ -624,6 +573,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
SelectedBaseModelType |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
NextPageCursor = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
partial void OnSelectedBaseModelTypeChanged(string value) |
|
|
|
@ -638,6 +588,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
|
|
|
|
|
value |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
NextPageCursor = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private async Task TrySearchAgain(bool shouldUpdatePageNumber = true) |
|
|
|
@ -648,18 +599,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; |
|
|
|
|