Browse Source

Merge pull request #575 from ionite34/model-browser-fixes

Converted civitai browser to new pagination cursor stuff
pull/629/head
JT 8 months ago committed by GitHub
parent
commit
0eb88e2255
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 2
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  3. 8
      StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs
  4. 178
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
  5. 66
      StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml
  6. 16
      StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml.cs
  7. 4
      StabilityMatrix.Core/Models/Api/CivitMetadata.cs
  8. 3
      StabilityMatrix.Core/Models/Api/CivitModelStats.cs
  9. 3
      StabilityMatrix.Core/Models/Api/CivitModelVersion.cs
  10. 26
      StabilityMatrix.Core/Models/Api/CivitModelsRequest.cs

6
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 ## v2.10.0-dev.3
### Added ### Added
- Added support for deep links from the new Stability Matrix Chrome extension - 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
- Fixed Inference HiresFix module "Inherit Primary Sampler Addons" setting not effectively disabling when unchecked - 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 - 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 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 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 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 ## v2.10.0-dev.2
### Added ### Added

2
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -325,7 +325,7 @@ public static class DesignData
); );
}*/ }*/
CivitAiBrowserViewModel.ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel> CivitAiBrowserViewModel.ModelCards = new ObservableCollectionExtended<CheckpointBrowserCardViewModel>
{ {
dialogFactory.Get<CheckpointBrowserCardViewModel>(vm => dialogFactory.Get<CheckpointBrowserCardViewModel>(vm =>
{ {

8
StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace StabilityMatrix.Avalonia.Models;
public interface IInfinitelyScroll
{
Task LoadNextPageAsync();
}

178
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs

@ -1,13 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncAwaitBestPractices; using AsyncAwaitBestPractices;
using Avalonia.Collections; using Avalonia.Collections;
@ -15,12 +12,15 @@ using Avalonia.Controls;
using Avalonia.Controls.Notifications; using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Alias;
using DynamicData.Binding;
using LiteDB; using LiteDB;
using LiteDB.Async; using LiteDB.Async;
using NLog; using NLog;
using OneOf.Types;
using Refit; using Refit;
using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
@ -41,24 +41,24 @@ namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
[View(typeof(CivitAiBrowserPage))] [View(typeof(CivitAiBrowserPage))]
[Singleton] [Singleton]
public partial class CivitAiBrowserViewModel : TabViewModelBase public partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScroll
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly ICivitApi civitApi; private readonly ICivitApi civitApi;
private readonly IDownloadService downloadService;
private readonly ISettingsManager settingsManager; private readonly ISettingsManager settingsManager;
private readonly ServiceManager<ViewModelBase> dialogFactory; private readonly ServiceManager<ViewModelBase> dialogFactory;
private readonly ILiteDbContext liteDbContext; private readonly ILiteDbContext liteDbContext;
private readonly INotificationService notificationService; private readonly INotificationService notificationService;
private const int MaxModelsPerPage = 20; private const int MaxModelsPerPage = 20;
private LRUCache< private LRUCache<
int /* model id */ int /* model id */
, ,
CheckpointBrowserCardViewModel CheckpointBrowserCardViewModel
> cache = new(50); > cache = new(150);
[ObservableProperty] [ObservableProperty]
private ObservableCollection<CheckpointBrowserCardViewModel>? modelCards; private ObservableCollection<CheckpointBrowserCardViewModel> modelCards = new();
[ObservableProperty] [ObservableProperty]
private DataGridCollectionView? modelCardsView; private DataGridCollectionView? modelCardsView;
@ -81,27 +81,9 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
[ObservableProperty] [ObservableProperty]
private CivitModelType selectedModelType = CivitModelType.Checkpoint; private CivitModelType selectedModelType = CivitModelType.Checkpoint;
[ObservableProperty]
private int currentPageNumber;
[ObservableProperty]
private int totalPages;
[ObservableProperty] [ObservableProperty]
private bool hasSearched; private bool hasSearched;
[ObservableProperty]
private bool canGoToNextPage;
[ObservableProperty]
private bool canGoToPreviousPage;
[ObservableProperty]
private bool canGoToFirstPage;
[ObservableProperty]
private bool canGoToLastPage;
[ObservableProperty] [ObservableProperty]
private bool isIndeterminate; private bool isIndeterminate;
@ -117,7 +99,8 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
[ObservableProperty] [ObservableProperty]
private bool showSantaHats = true; private bool showSantaHats = true;
private List<CheckpointBrowserCardViewModel> allModelCards = new(); [ObservableProperty]
private string? nextPageCursor;
public IEnumerable<CivitPeriod> AllCivitPeriods => public IEnumerable<CivitPeriod> AllCivitPeriods =>
Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>(); Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>();
@ -143,25 +126,11 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
) )
{ {
this.civitApi = civitApi; this.civitApi = civitApi;
this.downloadService = downloadService;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.dialogFactory = dialogFactory; this.dialogFactory = dialogFactory;
this.liteDbContext = liteDbContext; this.liteDbContext = liteDbContext;
this.notificationService = notificationService; 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; EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;
} }
@ -171,7 +140,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
return; return;
SearchQuery = $"$#{e}"; SearchQuery = $"$#{e}";
SearchModelsCommand.ExecuteAsync(null).SafeFireAndForget(); SearchModelsCommand.ExecuteAsync(false).SafeFireAndForget();
} }
public override void OnLoaded() public override void OnLoaded()
@ -223,7 +192,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
/// <summary> /// <summary>
/// Background update task /// Background update task
/// </summary> /// </summary>
private async Task CivitModelQuery(CivitModelsRequest request) private async Task CivitModelQuery(CivitModelsRequest request, bool isInfiniteScroll = false)
{ {
var timer = Stopwatch.StartNew(); var timer = Stopwatch.StartNew();
var queryText = request.Query; var queryText = request.Query;
@ -276,15 +245,9 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
} }
); );
if (cacheNew) UpdateModelCards(models, isInfiniteScroll);
{
Logger.Debug("New cache entry, updating model cards"); NextPageCursor = modelsResponse.Metadata?.NextCursor;
UpdateModelCards(models, modelsResponse.Metadata);
}
else
{
Logger.Debug("Cache entry already exists, not updating model cards");
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -327,7 +290,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
/// <summary> /// <summary>
/// Updates model cards using api response object. /// Updates model cards using api response object.
/// </summary> /// </summary>
private void UpdateModelCards(IEnumerable<CivitModel>? models, CivitMetadata? metadata) private void UpdateModelCards(List<CivitModel>? models, bool addCards = false)
{ {
if (models is null) if (models is null)
{ {
@ -335,7 +298,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
} }
else else
{ {
var updateCards = models var modelsToAdd = models
.Select(model => .Select(model =>
{ {
var cachedViewModel = cache.Get(model.Id); var cachedViewModel = cache.Get(model.Id);
@ -364,23 +327,34 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
return newCard; return newCard;
}) })
.ToList(); .Where(FilterModelCardsPredicate);
allModelCards = updateCards;
var filteredCards = updateCards.Where(FilterModelCardsPredicate);
if (SortMode == CivitSortMode.Installed) if (SortMode == CivitSortMode.Installed)
{ {
filteredCards = filteredCards.OrderByDescending(x => x.UpdateCardText == "Update Available"); modelsToAdd = modelsToAdd.OrderByDescending(x => x.UpdateCardText == "Update Available");
} }
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(filteredCards); 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.Add(model);
}
}
} }
TotalPages = metadata?.TotalPages ?? 1;
CanGoToFirstPage = CurrentPageNumber != 1;
CanGoToPreviousPage = CurrentPageNumber > 1;
CanGoToNextPage = CurrentPageNumber < TotalPages;
CanGoToLastPage = CurrentPageNumber != TotalPages;
// Status update // Status update
ShowMainLoadingSpinner = false; ShowMainLoadingSpinner = false;
IsIndeterminate = false; IsIndeterminate = false;
@ -390,27 +364,30 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
private string previousSearchQuery = string.Empty; private string previousSearchQuery = string.Empty;
[RelayCommand] [RelayCommand]
private async Task SearchModels() private async Task SearchModels(bool isInfiniteScroll = false)
{ {
var timer = Stopwatch.StartNew(); var timer = Stopwatch.StartNew();
if (SearchQuery != previousSearchQuery) if (SearchQuery != previousSearchQuery || !isInfiniteScroll)
{ {
// Reset page number // Reset page number
CurrentPageNumber = 1;
previousSearchQuery = SearchQuery; previousSearchQuery = SearchQuery;
NextPageCursor = null;
} }
// Build request // Build request
var modelRequest = new CivitModelsRequest var modelRequest = new CivitModelsRequest
{ {
Limit = MaxModelsPerPage,
Nsfw = "true", // Handled by local view filter Nsfw = "true", // Handled by local view filter
Sort = SortMode, Sort = SortMode,
Period = SelectedPeriod, Period = SelectedPeriod
Page = CurrentPageNumber
}; };
if (NextPageCursor != null)
{
modelRequest.Cursor = NextPageCursor;
}
if (SelectedModelType != CivitModelType.All) if (SelectedModelType != CivitModelType.All)
{ {
modelRequest.Types = [SelectedModelType]; modelRequest.Types = [SelectedModelType];
@ -516,14 +493,15 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
modelRequest.GetHashCode(), modelRequest.GetHashCode(),
elapsed.TotalSeconds elapsed.TotalSeconds
); );
UpdateModelCards(cachedQuery.Items, cachedQuery.Metadata); NextPageCursor = cachedQuery.Metadata?.NextCursor;
UpdateModelCards(cachedQuery.Items, isInfiniteScroll);
// Start remote query (background mode) // Start remote query (background mode)
// Skip when last query was less than 2 min ago // Skip when last query was less than 2 min ago
var timeSinceCache = DateTimeOffset.UtcNow - cachedQuery.InsertedAt; var timeSinceCache = DateTimeOffset.UtcNow - cachedQuery.InsertedAt;
if (timeSinceCache?.TotalMinutes >= 2) if (timeSinceCache?.TotalMinutes >= 2)
{ {
CivitModelQuery(modelRequest).SafeFireAndForget(); CivitModelQuery(modelRequest, isInfiniteScroll).SafeFireAndForget();
Logger.Debug( Logger.Debug(
"Cached query was more than 2 minutes ago ({Seconds:F0} s), updating cache with remote query", "Cached query was more than 2 minutes ago ({Seconds:F0} s), updating cache with remote query",
timeSinceCache.Value.TotalSeconds timeSinceCache.Value.TotalSeconds
@ -534,54 +512,23 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
{ {
// Not cached, wait for remote query // Not cached, wait for remote query
ShowMainLoadingSpinner = true; ShowMainLoadingSpinner = true;
await CivitModelQuery(modelRequest); await CivitModelQuery(modelRequest, isInfiniteScroll);
} }
UpdateResultsText(); 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() public void ClearSearchQuery()
{ {
SearchQuery = string.Empty; SearchQuery = string.Empty;
} }
partial void OnShowNsfwChanged(bool value) public async Task LoadNextPageAsync()
{ {
settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled, value); if (NextPageCursor != null)
// ModelCardsView?.Refresh(); {
var updateCards = allModelCards.Where(FilterModelCardsPredicate); await SearchModelsCommand.ExecuteAsync(true);
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(updateCards); }
if (!HasSearched)
return;
UpdateResultsText();
} }
partial void OnSelectedPeriodChanged(CivitPeriod value) partial void OnSelectedPeriodChanged(CivitPeriod value)
@ -596,6 +543,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
SelectedBaseModelType SelectedBaseModelType
) )
); );
NextPageCursor = null;
} }
partial void OnSortModeChanged(CivitSortMode value) partial void OnSortModeChanged(CivitSortMode value)
@ -610,6 +558,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
SelectedBaseModelType SelectedBaseModelType
) )
); );
NextPageCursor = null;
} }
partial void OnSelectedModelTypeChanged(CivitModelType value) partial void OnSelectedModelTypeChanged(CivitModelType value)
@ -624,6 +573,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
SelectedBaseModelType SelectedBaseModelType
) )
); );
NextPageCursor = null;
} }
partial void OnSelectedBaseModelTypeChanged(string value) partial void OnSelectedBaseModelTypeChanged(string value)
@ -638,6 +588,7 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
value value
) )
); );
NextPageCursor = null;
} }
private async Task TrySearchAgain(bool shouldUpdatePageNumber = true) private async Task TrySearchAgain(bool shouldUpdatePageNumber = true)
@ -648,18 +599,17 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
if (shouldUpdatePageNumber) if (shouldUpdatePageNumber)
{ {
CurrentPageNumber = 1; NextPageCursor = null;
} }
// execute command instead of calling method directly so that the IsRunning property gets updated // 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() private void UpdateResultsText()
{ {
NoResultsFound = ModelCards?.Count <= 0; NoResultsFound = ModelCards?.Count <= 0;
NoResultsText = NoResultsText = "No results found";
allModelCards.Count > 0 ? $"{allModelCards.Count} results hidden by filters" : "No results found";
} }
public override string Header => Resources.Label_CivitAi; public override string Header => Resources.Label_CivitAi;

66
StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml

@ -16,6 +16,7 @@
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
xmlns:controls1="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls" 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:DataContext="{x:Static designData:DesignData.CivitAiBrowserViewModel}"
d:DesignHeight="700" d:DesignHeight="700"
d:DesignWidth="800" d:DesignWidth="800"
@ -71,6 +72,7 @@
</UserControl.Styles> </UserControl.Styles>
<UserControl.Resources> <UserControl.Resources>
<system:Boolean x:Key="False">False</system:Boolean>
<converters:KiloFormatterStringConverter x:Key="KiloFormatterConverter"/> <converters:KiloFormatterStringConverter x:Key="KiloFormatterConverter"/>
<DataTemplate x:Key="CivitModelTemplate" DataType="{x:Type checkpointBrowser:CheckpointBrowserCardViewModel}"> <DataTemplate x:Key="CivitModelTemplate" DataType="{x:Type checkpointBrowser:CheckpointBrowserCardViewModel}">
<Border <Border
@ -107,7 +109,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
Height="75" Height="75"
ZIndex="10" ZIndex="10"
IsVisible="{Binding ShowSantaHats}" IsVisible="{Binding ShowSantaHats, FallbackValue=False}"
Margin="0,8,0,0" Margin="0,8,0,0"
Source="avares://StabilityMatrix.Avalonia/Assets/santahat.png"> Source="avares://StabilityMatrix.Avalonia/Assets/santahat.png">
<!-- <controls:BetterAdvancedImage.RenderTransform> --> <!-- <controls:BetterAdvancedImage.RenderTransform> -->
@ -336,7 +338,7 @@
<TextBlock <TextBlock
Margin="4,0" Margin="4,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding CivitModel.ModelVersionStats.FavoriteCount, Converter={StaticResource KiloFormatterConverter}}" /> Text="{Binding CivitModel.Stats.ThumbsUpCount, Converter={StaticResource KiloFormatterConverter}}" />
<avalonia:Icon Margin="4,0" Value="fa-solid fa-download" /> <avalonia:Icon Margin="4,0" Value="fa-solid fa-download" />
<TextBlock <TextBlock
@ -402,6 +404,7 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Classes="accent" Classes="accent"
Command="{Binding SearchModelsCommand}" Command="{Binding SearchModelsCommand}"
CommandParameter="{StaticResource False}"
IsDefault="True"> IsDefault="True">
<Grid> <Grid>
<controls:ProgressRing <controls:ProgressRing
@ -473,56 +476,17 @@
</ItemsRepeater> </ItemsRepeater>
</ScrollViewer> </ScrollViewer>
<TextBlock <TextBlock Grid.Row="2" Text="End of results"
Grid.Row="2" TextAlignment="Center"
Margin="8,8" Margin="0,0,0,8">
VerticalAlignment="Center" <TextBlock.IsVisible>
Text="{x:Static lang:Resources.Label_DataProvidedByCivitAi}" /> <MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="HasSearched"/>
<StackPanel Grid.Row="2" <Binding Path="NextPageCursor"
HorizontalAlignment="Center" Converter="{x:Static StringConverters.IsNullOrEmpty}"/>
IsVisible="{Binding HasSearched}" </MultiBinding>
Margin="0,8,0,8" </TextBlock.IsVisible>
Orientation="Horizontal">
<Button
Margin="0,0,8,0"
Command="{Binding FirstPage}"
IsEnabled="{Binding CanGoToFirstPage}"
ToolTip.Tip="{x:Static lang:Resources.Label_FirstPage}">
<avalonia:Icon Value="fa-solid fa-backward-fast" />
</Button>
<Button
Margin="0,0,16,0"
Command="{Binding PreviousPage}"
IsEnabled="{Binding CanGoToPreviousPage}"
ToolTip.Tip="{x:Static lang:Resources.Label_PreviousPage}">
<avalonia:Icon Value="fa-solid fa-caret-left" />
</Button>
<TextBlock Margin="8,0,4,0" TextAlignment="Center"
Text="{x:Static lang:Resources.Label_Page}"
VerticalAlignment="Center"/>
<ui:NumberBox Value="{Binding CurrentPageNumber, FallbackValue=1}"
VerticalAlignment="Center"
SpinButtonPlacementMode="Hidden"
TextAlignment="Center"/>
<TextBlock Margin="4,0,8,0" VerticalAlignment="Center">
<Run Text="/"/>
<Run Text="{Binding TotalPages, FallbackValue=5}"/>
</TextBlock> </TextBlock>
<Button
Margin="16,0,8,0"
Command="{Binding NextPage}"
IsEnabled="{Binding CanGoToNextPage}"
ToolTip.Tip="{x:Static lang:Resources.Label_NextPage}">
<avalonia:Icon Value="fa-solid fa-caret-right" />
</Button>
<Button
Command="{Binding LastPage}"
IsEnabled="{Binding CanGoToLastPage}"
ToolTip.Tip="{x:Static lang:Resources.Label_LastPage}">
<avalonia:Icon Value="fa-solid fa-forward-fast" />
</Button>
</StackPanel>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"

16
StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml.cs

@ -1,8 +1,11 @@
using System.Diagnostics; using System;
using System.Diagnostics;
using AsyncAwaitBestPractices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using CivitAiBrowserViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.CivitAiBrowserViewModel; using CivitAiBrowserViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.CivitAiBrowserViewModel;
@ -26,8 +29,15 @@ public partial class CivitAiBrowserPage : UserControlBase
if (sender is not ScrollViewer scrollViewer) if (sender is not ScrollViewer scrollViewer)
return; return;
var isAtEnd = scrollViewer.Offset == scrollViewer.ScrollBarMaximum; if (scrollViewer.Offset.Y == 0)
Debug.WriteLine($"IsAtEnd: {isAtEnd}"); return;
var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f;
if (isAtEnd && DataContext is IInfinitelyScroll scroll)
{
scroll.LoadNextPageAsync().SafeFireAndForget();
}
} }
private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) private void InputElement_OnKeyDown(object? sender, KeyEventArgs e)

4
StabilityMatrix.Core/Models/Api/CivitMetadata.cs

@ -2,7 +2,6 @@
namespace StabilityMatrix.Core.Models.Api; namespace StabilityMatrix.Core.Models.Api;
public class CivitMetadata public class CivitMetadata
{ {
[JsonPropertyName("totalItems")] [JsonPropertyName("totalItems")]
@ -22,4 +21,7 @@ public class CivitMetadata
[JsonPropertyName("prevPage")] [JsonPropertyName("prevPage")]
public string? PrevPage { get; set; } public string? PrevPage { get; set; }
[JsonPropertyName("nextCursor")]
public string? NextCursor { get; set; }
} }

3
StabilityMatrix.Core/Models/Api/CivitModelStats.cs

@ -9,4 +9,7 @@ public class CivitModelStats : CivitStats
[JsonPropertyName("commentCount")] [JsonPropertyName("commentCount")]
public int CommentCount { get; set; } public int CommentCount { get; set; }
[JsonPropertyName("thumbsUpCount")]
public int ThumbsUpCount { get; set; }
} }

3
StabilityMatrix.Core/Models/Api/CivitModelVersion.cs

@ -33,4 +33,7 @@ public class CivitModelVersion
[JsonPropertyName("stats")] [JsonPropertyName("stats")]
public CivitModelStats Stats { get; set; } public CivitModelStats Stats { get; set; }
[JsonPropertyName("publishedAt")]
public DateTimeOffset? PublishedAt { get; set; }
} }

26
StabilityMatrix.Core/Models/Api/CivitModelsRequest.cs

@ -2,7 +2,6 @@
namespace StabilityMatrix.Core.Models.Api; namespace StabilityMatrix.Core.Models.Api;
public class CivitModelsRequest public class CivitModelsRequest
{ {
/// <summary> /// <summary>
@ -121,18 +120,21 @@ public class CivitModelsRequest
[AliasAs("ids")] [AliasAs("ids")]
public string CommaSeparatedModelIds { get; set; } public string CommaSeparatedModelIds { get; set; }
[AliasAs("cursor")]
public string? Cursor { get; set; }
public override string ToString() public override string ToString()
{ {
return $"Page: {Page}, " + return $"Page: {Page}, "
$"Query: {Query}, " + + $"Query: {Query}, "
$"Tag: {Tag}, " + + $"Tag: {Tag}, "
$"Username: {Username}, " + + $"Username: {Username}, "
$"Types: {Types}, " + + $"Types: {Types}, "
$"Sort: {Sort}, " + + $"Sort: {Sort}, "
$"Period: {Period}, " + + $"Period: {Period}, "
$"Rating: {Rating}, " + + $"Rating: {Rating}, "
$"Nsfw: {Nsfw}, " + + $"Nsfw: {Nsfw}, "
$"BaseModel: {BaseModel}, " + + $"BaseModel: {BaseModel}, "
$"CommaSeparatedModelIds: {CommaSeparatedModelIds}"; + $"CommaSeparatedModelIds: {CommaSeparatedModelIds}";
} }
} }

Loading…
Cancel
Save