Browse Source

backport some bug fixes from dev

pull/530/head
JT 8 months ago
parent
commit
cbc37fb466
  1. 14
      CHANGELOG.md
  2. 2
      StabilityMatrix.Avalonia/App.axaml
  3. 3
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  4. 8
      StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs
  5. 178
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
  6. 6
      StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
  7. 5
      StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
  8. 7
      StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
  9. 19
      StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
  10. 82
      StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml
  11. 16
      StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml.cs
  12. 39
      StabilityMatrix.Avalonia/Views/Dialogs/NewOneClickInstallDialog.axaml
  13. 4
      StabilityMatrix.Avalonia/Views/MainWindow.axaml
  14. 14
      StabilityMatrix.Core/Models/Api/CivitMetadata.cs
  15. 5
      StabilityMatrix.Core/Models/Api/CivitModelStats.cs
  16. 21
      StabilityMatrix.Core/Models/Api/CivitModelVersion.cs
  17. 60
      StabilityMatrix.Core/Models/Api/CivitModelsRequest.cs
  18. 12
      StabilityMatrix.Core/Models/PackageDifficulty.cs
  19. 2
      StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs
  20. 2
      StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs
  21. 2
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  22. 2
      StabilityMatrix.Core/Models/Packages/SDWebForge.cs

14
CHANGELOG.md

@ -5,6 +5,20 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
## v2.9.2
### 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 models not being removed from the installed models cache when deleting them from the Checkpoints page
- Fixed model download location options for VAEs in the CivitAI Model Browser
- Fixed One-Click install progress dialog not disappearing after completion
- 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 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.9.1
### Added
- Fixed [#498](https://github.com/LykosAI/StabilityMatrix/issues/498) Added "Pony" category to CivitAI Model Browser

2
StabilityMatrix.Avalonia/App.axaml

@ -4,6 +4,7 @@
xmlns:local="using:StabilityMatrix.Avalonia"
xmlns:idcr="using:Dock.Avalonia.Controls.Recycling"
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
xmlns:controls="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
Name="Stability Matrix"
RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
@ -79,6 +80,7 @@
<StyleInclude Source="Controls/Inference/FreeUCard.axaml"/>
<StyleInclude Source="Controls/Inference/ControlNetCard.axaml"/>
<StyleInclude Source="Controls/Inference/PromptExpansionCard.axaml"/>
<controls:ControlThemes/>
<Style Selector="DockControl">
<Setter Property="(DockProperties.ControlRecycling)" Value="{StaticResource ControlRecyclingKey}" />

3
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -404,7 +404,8 @@ public static class DesignData
new PackageInstallProgressItemViewModel(
new PackageModificationRunner
{
CurrentProgress = new ProgressReport(0.5f, "Installing package...")
CurrentProgress = new ProgressReport(0.5f, "Installing package..."),
ModificationCompleteMessage = "Done"
}
)
}

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.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;

6
StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs

@ -8,6 +8,7 @@ using Avalonia.Data;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.DependencyInjection;
using NLog;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
@ -136,6 +137,11 @@ public partial class CheckpointFile : ViewModelBase
{
await Task.Run(() => File.Delete(cmInfoPath));
}
var settingsManager = App.Services.GetRequiredService<ISettingsManager>();
settingsManager.Transaction(s =>
{
s.InstalledModelHashes?.Remove(ConnectedModel.Hashes.BLAKE3);
});
}
}
catch (IOException ex)

5
StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs

@ -85,6 +85,10 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
.Subscribe();
AllPackagesCache.AddOrUpdate(packageFactory.GetAllAvailablePackages());
if (ShownPackages.Count > 0)
return;
ShowIncompatiblePackages = true;
}
[RelayCommand]
@ -182,6 +186,7 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
{
ShowDialogOnStart = false,
HideCloseButton = false,
ModificationCompleteMessage = $"{selectedPackage.DisplayName} installed successfully"
};
runner

7
StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs

@ -192,7 +192,12 @@ public partial class OneClickInstallViewModel : ContentDialogViewModelBase
var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, installedPackage);
steps.Add(addInstalledPackageStep);
var runner = new PackageModificationRunner { ShowDialogOnStart = true, HideCloseButton = true, };
var runner = new PackageModificationRunner
{
ShowDialogOnStart = true,
HideCloseButton = true,
ModificationCompleteMessage = $"{SelectedPackage.DisplayName} installed successfully",
};
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);

19
StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs

@ -91,7 +91,6 @@ public partial class SelectModelVersionViewModel(
{
SelectedVersionViewModel = Versions[0];
CanGoToNextImage = true;
LoadInstallLocations();
}
partial void OnSelectedVersionViewModelChanged(ModelVersionViewModel? value)
@ -130,6 +129,8 @@ public partial class SelectModelVersionViewModel(
var canImport = true;
if (settingsManager.IsLibraryDirSet)
{
LoadInstallLocations();
var fileSizeBytes = value?.CivitFile.SizeKb * 1024;
var freeSizeBytes =
SystemInfo.GetDiskFreeSpaceBytes(settingsManager.ModelsDirectory) ?? long.MaxValue;
@ -151,9 +152,9 @@ public partial class SelectModelVersionViewModel(
IsImportEnabled = value?.CivitFile != null && canImport && !ShowEmptyPathWarning;
}
partial void OnSelectedInstallLocationChanged(string value)
partial void OnSelectedInstallLocationChanged(string? value)
{
if (value.Equals("Custom...", StringComparison.OrdinalIgnoreCase))
if (value?.Equals("Custom...", StringComparison.OrdinalIgnoreCase) is true)
{
Dispatcher.UIThread.InvokeAsync(SelectCustomFolder);
}
@ -304,14 +305,16 @@ public partial class SelectModelVersionViewModel(
private void LoadInstallLocations()
{
var installLocations = new ObservableCollection<string>();
var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);
var downloadDirectory = rootModelsDirectory.JoinDir(
CivitModel.Type.ConvertTo<SharedFolderType>().GetStringValue()
SelectedFile?.CivitFile.Type == CivitFileType.VAE
? SharedFolderType.VAE.GetStringValue()
: CivitModel.Type.ConvertTo<SharedFolderType>().GetStringValue()
);
var installLocations = new ObservableCollection<string>
{
downloadDirectory.ToString().Replace(rootModelsDirectory, "Models")
};
installLocations.Add(downloadDirectory.ToString().Replace(rootModelsDirectory, "Models"));
foreach (var directory in downloadDirectory.EnumerateDirectories())
{

82
StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml

@ -15,6 +15,8 @@
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
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"
@ -70,6 +72,7 @@
</UserControl.Styles>
<UserControl.Resources>
<system:Boolean x:Key="False">False</system:Boolean>
<converters:KiloFormatterStringConverter x:Key="KiloFormatterConverter"/>
<DataTemplate x:Key="CivitModelTemplate" DataType="{x:Type checkpointBrowser:CheckpointBrowserCardViewModel}">
<Border
@ -100,28 +103,27 @@
CommandParameter="{Binding CivitModel}"
IsEnabled="{Binding !IsImporting}">
<Grid RowDefinitions="*, Auto">
<controls:BetterAdvancedImage Grid.Row="0"
<controls1:AsyncImage Grid.Row="0"
CornerRadius="8"
VerticalAlignment="Top"
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">
<!-- <controls:BetterAdvancedImage.RenderTransform> -->
<!-- <RotateTransform Angle="315"></RotateTransform> -->
<!-- </controls:BetterAdvancedImage.RenderTransform> -->
</controls:BetterAdvancedImage>
<controls:BetterAdvancedImage
</controls1:AsyncImage>
<controls1:AsyncImage
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
Width="330"
Height="400"
Source="{Binding CardImage}"
Stretch="UniformToFill"
StretchDirection="Both"/>
Stretch="UniformToFill"/>
<StackPanel
Grid.Row="0"
@ -180,7 +182,7 @@
Classes="transparent"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<controls:BetterAdvancedImage
<controls1:AsyncImage
Width="22"
Height="22"
Effect="{StaticResource ImageDropShadowEffect}"
@ -336,7 +338,7 @@
<TextBlock
Margin="4,0"
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" />
<TextBlock
@ -402,6 +404,7 @@
VerticalAlignment="Stretch"
Classes="accent"
Command="{Binding SearchModelsCommand}"
CommandParameter="{StaticResource False}"
IsDefault="True">
<Grid>
<controls:ProgressRing
@ -472,57 +475,18 @@
</ItemsRepeater.Layout>
</ItemsRepeater>
</ScrollViewer>
<TextBlock
Grid.Row="2"
Margin="8,8"
VerticalAlignment="Center"
Text="{x:Static lang:Resources.Label_DataProvidedByCivitAi}" />
<StackPanel Grid.Row="2"
HorizontalAlignment="Center"
IsVisible="{Binding HasSearched}"
Margin="0,8,0,8"
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>
<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 Grid.Row="2" Text="End of results"
TextAlignment="Center"
Margin="0,0,0,8">
<TextBlock.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="HasSearched"/>
<Binding Path="NextPageCursor"
Converter="{x:Static StringConverters.IsNullOrEmpty}"/>
</MultiBinding>
</TextBlock.IsVisible>
</TextBlock>
<TextBlock
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.Input;
using Avalonia.Markup.Xaml;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Core.Attributes;
using CivitAiBrowserViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.CivitAiBrowserViewModel;
@ -26,8 +29,15 @@ public partial class CivitAiBrowserPage : UserControlBase
if (sender is not ScrollViewer scrollViewer)
return;
var isAtEnd = scrollViewer.Offset == scrollViewer.ScrollBarMaximum;
Debug.WriteLine($"IsAtEnd: {isAtEnd}");
if (scrollViewer.Offset.Y == 0)
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)

39
StabilityMatrix.Avalonia/Views/Dialogs/NewOneClickInstallDialog.axaml

@ -62,6 +62,27 @@
VerticalAlignment="Top">
<controls:Card.Styles>
<Style Selector="controls|Card[Tag=ReallyRecommended]">
<Setter Property="Background"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="Recommended"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=Recommended]">
<Setter Property="Background"
Value="{DynamicResource ThemeGreenColorTransparent}" />
@ -117,6 +138,24 @@
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=InferenceCompatible]">
<Setter Property="Background"
Value="{DynamicResource ThemePurpleColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemePurpleColor}" />
<Setter Property="Foreground"
Value="White" />
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="Inference Compatible"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card">
<Setter Property="Background"
Value="{DynamicResource ThemeDarkRedColor}" />

4
StabilityMatrix.Avalonia/Views/MainWindow.axaml

@ -14,8 +14,8 @@
d:DataContext="{x:Static mocks:DesignData.MainWindowViewModel}"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/Icon.ico"
Width="1100"
Height="750"
Width="1300"
Height="950"
BackRequested="TopLevel_OnBackRequested"
Title="Stability Matrix"
DockProperties.IsDragEnabled="True"

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

@ -2,24 +2,26 @@
namespace StabilityMatrix.Core.Models.Api;
public class CivitMetadata
{
[JsonPropertyName("totalItems")]
public int TotalItems { get; set; }
[JsonPropertyName("currentPage")]
public int CurrentPage { get; set; }
[JsonPropertyName("pageSize")]
public int PageSize { get; set; }
[JsonPropertyName("totalPages")]
public int TotalPages { get; set; }
[JsonPropertyName("nextPage")]
public string? NextPage { get; set; }
[JsonPropertyName("prevPage")]
public string? PrevPage { get; set; }
[JsonPropertyName("nextCursor")]
public string? NextCursor { get; set; }
}

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

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

21
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<CivitFile>? Files { get; set; }
[JsonPropertyName("images")]
public List<CivitImage>? Images { get; set; }
[JsonPropertyName("stats")]
public CivitModelStats Stats { get; set; }
[JsonPropertyName("publishedAt")]
public DateTimeOffset? PublishedAt { get; set; }
}

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

@ -2,7 +2,6 @@
namespace StabilityMatrix.Core.Models.Api;
public class CivitModelsRequest
{
/// <summary>
@ -10,99 +9,99 @@ public class CivitModelsRequest
/// </summary>
[AliasAs("limit")]
public int? Limit { get; set; }
/// <summary>
/// The page from which to start fetching models
/// </summary>
[AliasAs("page")]
public int? Page { get; set; }
/// <summary>
/// Search query to filter models by name
/// </summary>
[AliasAs("query")]
public string? Query { get; set; }
/// <summary>
/// Search query to filter models by tag
/// </summary>
[AliasAs("tag")]
public string? Tag { get; set; }
/// <summary>
/// Search query to filter models by user
/// </summary>
[AliasAs("username")]
public string? Username { get; set; }
/// <summary>
/// The type of model you want to filter with. If none is specified, it will return all types
/// </summary>
[AliasAs("types")]
public CivitModelType[]? Types { get; set; }
/// <summary>
/// The order in which you wish to sort the results
/// </summary>
[AliasAs("sort")]
public CivitSortMode? Sort { get; set; }
/// <summary>
/// The time frame in which the models will be sorted
/// </summary>
[AliasAs("period")]
public CivitPeriod? Period { get; set; }
/// <summary>
/// The rating you wish to filter the models with. If none is specified, it will return models with any rating
/// </summary>
[AliasAs("rating")]
public int? Rating { get; set; }
/// <summary>
/// Filter to models that require or don't require crediting the creator
/// <remarks>Requires Authentication</remarks>
/// </summary>
[AliasAs("favorites")]
public bool? Favorites { get; set; }
/// <summary>
/// Filter to hidden models of the authenticated user
/// <remarks>Requires Authentication</remarks>
/// </summary>
[AliasAs("hidden")]
public bool? Hidden { get; set; }
/// <summary>
/// Only include the primary file for each model (This will use your preferred format options if you use an API token or session cookie)
/// </summary>
[AliasAs("primaryFileOnly")]
public bool? PrimaryFileOnly { get; set; }
/// <summary>
/// Filter to models that allow or don't allow creating derivatives
/// </summary>
[AliasAs("allowDerivatives")]
public bool? AllowDerivatives { get; set; }
/// <summary>
/// Filter to models that allow or don't allow derivatives to have a different license
/// </summary>
[AliasAs("allowDifferentLicenses")]
public bool? AllowDifferentLicenses { get; set; }
/// <summary>
/// Filter to models based on their commercial permissions
/// </summary>
[AliasAs("allowCommercialUse")]
public CivitCommercialUse? AllowCommercialUse { get; set; }
/// <summary>
/// If false, will return safer images and hide models that don't have safe images
/// </summary>
[AliasAs("nsfw")]
public string? Nsfw { get; set; }
/// <summary>
/// options: <br/>
/// SD 1.4 <br/>
@ -117,22 +116,25 @@ public class CivitModelsRequest
/// </summary>
[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}";
}
}

12
StabilityMatrix.Core/Models/PackageDifficulty.cs

@ -2,11 +2,13 @@
public enum PackageDifficulty
{
ReallyRecommended = -1,
Recommended = 0,
Simple = 1,
Advanced = 2,
Expert = 3,
Nightmare = 4,
UltraNightmare = 5,
InferenceCompatible = 1,
Simple = 2,
Advanced = 3,
Expert = 4,
Nightmare = 5,
UltraNightmare = 6,
Impossible = 999
}

2
StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs

@ -32,7 +32,7 @@ public interface IPackageModificationRunner
string? ModificationCompleteTitle { get; init; }
string? ModificationCompleteMessage { get; init; }
string ModificationCompleteMessage { get; init; }
string? ModificationFailedTitle { get; init; }

2
StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs

@ -80,7 +80,7 @@ public class PackageModificationRunner : IPackageModificationRunner
public string? ModificationCompleteTitle { get; init; } = "Install Complete";
public string? ModificationCompleteMessage { get; init; }
public required string ModificationCompleteMessage { get; init; }
public string? ModificationFailedTitle { get; init; } = "Install Failed";

2
StabilityMatrix.Core/Models/Packages/ComfyUI.cs

@ -43,7 +43,7 @@ public class ComfyUI(
public override bool ShouldIgnoreReleases => true;
public override bool IsInferenceCompatible => true;
public override string OutputFolderName => "output";
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.InferenceCompatible;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;

2
StabilityMatrix.Core/Models/Packages/SDWebForge.cs

@ -35,6 +35,8 @@ public class SDWebForge(
"https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/ca5e05ed-bd86-4ced-8662-f41034648e8c"
);
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended;
public override string MainBranch => "main";
public override bool ShouldIgnoreReleases => true;
public override IPackageExtensionManager ExtensionManager => null;

Loading…
Cancel
Save