Ionite
1 year ago
committed by
GitHub
52 changed files with 1814 additions and 100 deletions
@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks; |
||||
using Refit; |
||||
using StabilityMatrix.Models.Api; |
||||
|
||||
namespace StabilityMatrix.Api; |
||||
|
||||
public interface ICivitApi |
||||
{ |
||||
[Get("/api/v1/models")] |
||||
Task<CivitModelsResponse> GetModels(CivitModelsRequest request); |
||||
|
||||
[Get("/api/v1/model-versions/by-hash")] |
||||
Task<CivitModelVersion> GetModelVersionByHash([Query] string hash); |
||||
} |
@ -0,0 +1,209 @@
|
||||
<Page |
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" |
||||
d:DataContext="{d:DesignInstance designData:MockCheckpointBrowserViewModel, |
||||
IsDesignTimeCreatable=True}" |
||||
d:DesignHeight="600" |
||||
d:DesignWidth="650" |
||||
mc:Ignorable="d" |
||||
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}" |
||||
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}" |
||||
x:Class="StabilityMatrix.CheckpointBrowserPage" |
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
xmlns:api="clr-namespace:StabilityMatrix.Models.Api" |
||||
xmlns:converters="clr-namespace:StabilityMatrix.Converters" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:designData="clr-namespace:StabilityMatrix.DesignData" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:models="clr-namespace:StabilityMatrix.Models" |
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" |
||||
xmlns:viewModels="clr-namespace:StabilityMatrix.ViewModels" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
||||
<Page.Resources> |
||||
<converters:ValueConverterGroup x:Key="InvertAndVisibilitate"> |
||||
<converters:BoolNegationConverter /> |
||||
<BooleanToVisibilityConverter /> |
||||
</converters:ValueConverterGroup> |
||||
|
||||
<converters:UriToBitmapConverter x:Key="UriToBitmapConverter" /> |
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" /> |
||||
|
||||
<DataTemplate DataType="{x:Type viewModels:CheckpointBrowserCardViewModel}" x:Key="CivitModelTemplate"> |
||||
<ui:Card MaxHeight="450" Width="330"> |
||||
<StackPanel Orientation="Vertical"> |
||||
<TextBlock |
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" |
||||
Margin="0,0,0,0" |
||||
Text="{Binding CivitModel.Name}" |
||||
VerticalAlignment="Center" /> |
||||
<TextBlock |
||||
FontSize="11" |
||||
Foreground="{DynamicResource TextFillColorTertiaryBrush}" |
||||
Margin="0,2,0,0" |
||||
Text="{Binding CivitModel.ModelVersions[0].Name}" |
||||
VerticalAlignment="Center" /> |
||||
<Grid> |
||||
<Image |
||||
Margin="0,8,0,8" |
||||
MaxHeight="300" |
||||
Source="{Binding CivitModel.ModelVersions[0].Images[0].Url, Converter={StaticResource UriToBitmapConverter}}" |
||||
Stretch="UniformToFill" /> |
||||
<ui:Button |
||||
Appearance="Info" |
||||
Command="{Binding OpenModelCommand}" |
||||
CommandParameter="{Binding CivitModel}" |
||||
HorizontalAlignment="Right" |
||||
Margin="0,16,8,0" |
||||
VerticalAlignment="Top"> |
||||
<ui:SymbolIcon Symbol="Open48" /> |
||||
</ui:Button> |
||||
|
||||
<Rectangle |
||||
Fill="#DD000000" |
||||
HorizontalAlignment="Stretch" |
||||
Margin="0,8,0,8" |
||||
VerticalAlignment="Stretch" |
||||
Visibility="{Binding ImportCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}" /> |
||||
<StackPanel |
||||
HorizontalAlignment="Stretch" |
||||
Orientation="Vertical" |
||||
VerticalAlignment="Center" |
||||
Visibility="{Binding ImportCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}"> |
||||
<ui:ProgressRing |
||||
HorizontalAlignment="Center" |
||||
IsIndeterminate="False" |
||||
Progress="{Binding Value}" |
||||
VerticalAlignment="Center" /> |
||||
<TextBlock |
||||
HorizontalAlignment="Center" |
||||
Margin="0,8,0,0" |
||||
Text="{Binding Text, FallbackValue=Importing...}" |
||||
VerticalAlignment="Center" /> |
||||
</StackPanel> |
||||
</Grid> |
||||
<Grid> |
||||
<Grid.ColumnDefinitions> |
||||
<ColumnDefinition Width="1*" /> |
||||
</Grid.ColumnDefinitions> |
||||
<ui:Button |
||||
Appearance="Primary" |
||||
Command="{Binding ImportCommand}" |
||||
CommandParameter="{Binding CivitModel}" |
||||
Content="Import" |
||||
HorizontalAlignment="Stretch" |
||||
Margin="0,8,0,0" /> |
||||
</Grid> |
||||
</StackPanel> |
||||
</ui:Card> |
||||
</DataTemplate> |
||||
</Page.Resources> |
||||
|
||||
<Grid> |
||||
<Grid.RowDefinitions> |
||||
<RowDefinition Height="Auto" /> |
||||
<RowDefinition Height="*" /> |
||||
<RowDefinition Height="Auto" /> |
||||
</Grid.RowDefinitions> |
||||
|
||||
<StackPanel Margin="8" Orientation="Vertical"> |
||||
<Grid> |
||||
<Grid.ColumnDefinitions> |
||||
<ColumnDefinition Width="*" /> |
||||
<ColumnDefinition Width="Auto" /> |
||||
</Grid.ColumnDefinitions> |
||||
|
||||
<ui:TextBox |
||||
HorizontalAlignment="Stretch" |
||||
Margin="8,0,0,0" |
||||
PlaceholderText="Query" |
||||
Text="{Binding SearchQuery, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> |
||||
|
||||
<ui:Button |
||||
Appearance="Primary" |
||||
Command="{Binding SearchModelsCommand}" |
||||
Grid.Column="1" |
||||
IsDefault="True" |
||||
Margin="8,0,8,0" |
||||
VerticalAlignment="Stretch" |
||||
Width="80"> |
||||
<StackPanel Orientation="Horizontal"> |
||||
<ui:ProgressRing |
||||
Height="20" |
||||
IsIndeterminate="True" |
||||
VerticalAlignment="Center" |
||||
Visibility="{Binding SearchModelsCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}" |
||||
Width="20" /> |
||||
<TextBlock |
||||
Text="Search" |
||||
VerticalAlignment="Center" |
||||
Visibility="{Binding SearchModelsCommand.IsRunning, Converter={StaticResource InvertAndVisibilitate}}" /> |
||||
</StackPanel> |
||||
</ui:Button> |
||||
</Grid> |
||||
<DockPanel> |
||||
<StackPanel Margin="8" Orientation="Vertical"> |
||||
<Label Content="Sort" /> |
||||
<ComboBox |
||||
ItemsSource="{Binding AllSortModes}" |
||||
MinWidth="100" |
||||
SelectedItem="{Binding SortMode}" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel Margin="0,8" Orientation="Vertical"> |
||||
<Label Content="Period" /> |
||||
<ComboBox |
||||
ItemsSource="{Binding AllCivitPeriods}" |
||||
MinWidth="100" |
||||
SelectedItem="{Binding SelectedPeriod}" /> |
||||
</StackPanel> |
||||
|
||||
<CheckBox |
||||
Content="Show NSFW Content" |
||||
FontSize="12" |
||||
HorizontalAlignment="Right" |
||||
IsChecked="{Binding ShowNsfw, Mode=TwoWay}" |
||||
Margin="8,8,8,0" /> |
||||
</DockPanel> |
||||
|
||||
</StackPanel> |
||||
|
||||
<ui:DynamicScrollViewer CanContentScroll="True" Grid.Row="1"> |
||||
<ui:VirtualizingGridView |
||||
ItemTemplate="{StaticResource CivitModelTemplate}" |
||||
ItemsSource="{Binding ModelCards}" |
||||
PreviewMouseWheel="VirtualizingGridView_OnPreviewMouseWheel" |
||||
SpacingMode="StartAndEndOnly" /> |
||||
</ui:DynamicScrollViewer> |
||||
|
||||
<StackPanel |
||||
Grid.Row="2" |
||||
HorizontalAlignment="Center" |
||||
Margin="8" |
||||
Orientation="Vertical" |
||||
Visibility="{Binding HasSearched, Converter={StaticResource BoolToVisibilityConverter}}"> |
||||
<TextBlock Margin="0,0,4,4" TextAlignment="Center"> |
||||
<Run Text="Page" /> |
||||
<Run Text="{Binding CurrentPageNumber, FallbackValue=1}" /> |
||||
<Run Text="/" /> |
||||
<Run Text="{Binding TotalPages, FallbackValue=5}" /> |
||||
</TextBlock> |
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal"> |
||||
<ui:Button |
||||
Command="{Binding PreviousPageCommand}" |
||||
IsEnabled="{Binding CanGoToPreviousPage}" |
||||
Margin="0,0,8,0"> |
||||
<ui:SymbolIcon Symbol="ArrowPrevious24" /> |
||||
</ui:Button> |
||||
<ui:Button Command="{Binding NextPageCommand}" IsEnabled="{Binding CanGoToNextPage}"> |
||||
<ui:SymbolIcon Symbol="ArrowNext24" /> |
||||
</ui:Button> |
||||
</StackPanel> |
||||
</StackPanel> |
||||
|
||||
<ui:ProgressRing |
||||
Grid.Row="0" |
||||
Grid.RowSpan="3" |
||||
IsIndeterminate="True" |
||||
Visibility="{Binding SearchModelsCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}" /> |
||||
</Grid> |
||||
</Page> |
@ -0,0 +1,36 @@
|
||||
using System.Diagnostics; |
||||
using System.Threading; |
||||
using System.Windows; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Input; |
||||
using System.Windows.Media; |
||||
using System.Windows.Media.Effects; |
||||
using StabilityMatrix.ViewModels; |
||||
using Wpf.Ui.Controls; |
||||
|
||||
namespace StabilityMatrix; |
||||
|
||||
public partial class CheckpointBrowserPage : Page |
||||
{ |
||||
public CheckpointBrowserPage(CheckpointBrowserViewModel viewModel) |
||||
{ |
||||
InitializeComponent(); |
||||
DataContext = viewModel; |
||||
} |
||||
|
||||
private void VirtualizingGridView_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) |
||||
{ |
||||
if (e.Handled) return; |
||||
|
||||
e.Handled = true; |
||||
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) |
||||
{ |
||||
RoutedEvent = MouseWheelEvent, |
||||
Source = sender |
||||
}; |
||||
if (((Control)sender).Parent is UIElement parent) |
||||
{ |
||||
parent.RaiseEvent(eventArg); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,32 @@
|
||||
using System; |
||||
using System.Globalization; |
||||
using System.Windows; |
||||
using System.Windows.Data; |
||||
|
||||
namespace StabilityMatrix.Converters; |
||||
|
||||
public class BooleanToHiddenVisibleConverter : IValueConverter |
||||
{ |
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
||||
{ |
||||
var bValue = false; |
||||
if (value is bool b) |
||||
{ |
||||
bValue = b; |
||||
} |
||||
else if (value is bool) |
||||
{ |
||||
var tmp = (bool?) value; |
||||
bValue = tmp.Value; |
||||
} |
||||
return bValue ? Visibility.Visible : Visibility.Hidden; |
||||
} |
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
||||
{ |
||||
if (value is Visibility visibility) |
||||
{ |
||||
return visibility == Visibility.Visible; |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,39 @@
|
||||
using System.Collections.ObjectModel; |
||||
using System.ComponentModel; |
||||
using StabilityMatrix.Models.Api; |
||||
using StabilityMatrix.ViewModels; |
||||
|
||||
namespace StabilityMatrix.DesignData; |
||||
|
||||
[DesignOnly(true)] |
||||
public class MockCheckpointBrowserViewModel : CheckpointBrowserViewModel |
||||
{ |
||||
public MockCheckpointBrowserViewModel() : base(null!, null!, null!) |
||||
{ |
||||
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel> |
||||
{ |
||||
new (null!, null!, null!) |
||||
{ |
||||
CivitModel = new() |
||||
{ |
||||
Name = "bb95 Furry Mix", |
||||
ModelVersions = new[] |
||||
{ |
||||
new CivitModelVersion |
||||
{ |
||||
Name = "v7.0", |
||||
Images = new[] |
||||
{ |
||||
new CivitImage |
||||
{ |
||||
Url = |
||||
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/1547f350-461a-4cd0-a753-0544aa81e4fc/width=450/00000-4137473915.jpeg" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,56 @@
|
||||
using System; |
||||
using System.Linq; |
||||
using System.Windows.Ink; |
||||
|
||||
namespace StabilityMatrix.Extensions; |
||||
|
||||
public static class EnumAttributeExtensions |
||||
{ |
||||
private static T? GetAttributeValue<T>(Enum value) |
||||
{ |
||||
var type = value.GetType(); |
||||
var fieldInfo = type.GetField(value.ToString()); |
||||
// Get the string value attributes |
||||
var attribs = fieldInfo?.GetCustomAttributes(typeof(T), false) as T[]; |
||||
// Return the first if there was a match. |
||||
return attribs?.Length > 0 ? attribs[0] : default; |
||||
} |
||||
/// <summary> |
||||
/// Gets the StringValue field attribute on a given enum value. |
||||
/// If not found, returns the enum value itself as a string. |
||||
/// </summary> |
||||
/// <param name="value"></param> |
||||
/// <returns></returns> |
||||
public static string GetStringValue(this Enum value) |
||||
{ |
||||
var attr = GetAttributeValue<StringValueAttribute>(value)?.StringValue; |
||||
return attr ?? Enum.GetName(value.GetType(), value)!; |
||||
} |
||||
/// <summary> |
||||
/// Gets the Description field attribute on a given enum value. |
||||
/// </summary> |
||||
/// <param name="value"></param> |
||||
/// <returns></returns> |
||||
public static string? GetDescription(this Enum value) |
||||
{ |
||||
return GetAttributeValue<DescriptionAttribute>(value)?.Description; |
||||
} |
||||
} |
||||
|
||||
[AttributeUsage(AttributeTargets.Field)] |
||||
public sealed class StringValueAttribute : Attribute |
||||
{ |
||||
public string StringValue { get; } |
||||
public StringValueAttribute(string value) { |
||||
StringValue = value; |
||||
} |
||||
} |
||||
|
||||
[AttributeUsage(AttributeTargets.Field)] |
||||
public sealed class DescriptionAttribute : Attribute |
||||
{ |
||||
public string Description { get; } |
||||
public DescriptionAttribute(string value) { |
||||
Description = value; |
||||
} |
||||
} |
@ -0,0 +1,26 @@
|
||||
using System; |
||||
|
||||
namespace StabilityMatrix.Extensions; |
||||
|
||||
public static class EnumConversionExtensions |
||||
{ |
||||
public static T? ConvertTo<T>(this Enum value) where T : Enum |
||||
{ |
||||
var type = value.GetType(); |
||||
var fieldInfo = type.GetField(value.ToString()); |
||||
// Get the string value attributes |
||||
var attribs = fieldInfo?.GetCustomAttributes(typeof(ConvertToAttribute<T>), false) as ConvertToAttribute<T>[]; |
||||
// Return the first if there was a match. |
||||
return attribs?.Length > 0 ? attribs[0].ConvertToEnum : default; |
||||
} |
||||
} |
||||
|
||||
[AttributeUsage(AttributeTargets.Field)] |
||||
public sealed class ConvertToAttribute<T> : Attribute where T : Enum |
||||
{ |
||||
public T ConvertToEnum { get; } |
||||
public ConvertToAttribute(T toEnum) |
||||
{ |
||||
ConvertToEnum = toEnum; |
||||
} |
||||
} |
@ -0,0 +1,61 @@
|
||||
using System; |
||||
using System.Buffers; |
||||
using System.IO; |
||||
using System.Security.Cryptography; |
||||
using System.Threading.Tasks; |
||||
using StabilityMatrix.Models; |
||||
|
||||
namespace StabilityMatrix.Helper; |
||||
|
||||
public static class FileHash |
||||
{ |
||||
public static async Task<string> GetHashAsync(HashAlgorithm hashAlgorithm, Stream stream, byte[] buffer, Action<ulong>? progress = default) |
||||
{ |
||||
ulong totalBytesRead = 0; |
||||
|
||||
using (hashAlgorithm) |
||||
{ |
||||
int bytesRead; |
||||
while ((bytesRead = await stream.ReadAsync(buffer)) != 0) |
||||
{ |
||||
totalBytesRead += (ulong) bytesRead; |
||||
hashAlgorithm.TransformBlock(buffer, 0, bytesRead, null, 0); |
||||
progress?.Invoke(totalBytesRead); |
||||
} |
||||
hashAlgorithm.TransformFinalBlock(buffer, 0, 0); |
||||
var hash = hashAlgorithm.Hash; |
||||
if (hash == null || hash.Length == 0) |
||||
{ |
||||
throw new InvalidOperationException("Hash algorithm did not produce a hash."); |
||||
} |
||||
return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant(); |
||||
} |
||||
} |
||||
|
||||
public static async Task<string> GetSha256Async(string filePath, IProgress<ProgressReport>? progress = default) |
||||
{ |
||||
if (!File.Exists(filePath)) |
||||
{ |
||||
throw new FileNotFoundException($"Could not find file: {filePath}"); |
||||
} |
||||
|
||||
var totalBytes = Convert.ToUInt64(new FileInfo(filePath).Length); |
||||
var shared = ArrayPool<byte>.Shared; |
||||
var buffer = shared.Rent((int) FileTransfers.GetBufferSize(totalBytes)); |
||||
try |
||||
{ |
||||
await using var stream = File.OpenRead(filePath); |
||||
|
||||
var hash = await GetHashAsync(SHA256.Create(), stream, buffer, totalBytesRead => |
||||
{ |
||||
progress?.Report(new ProgressReport(totalBytesRead, totalBytes)); |
||||
}); |
||||
return hash; |
||||
} |
||||
finally |
||||
{ |
||||
shared.Return(buffer); |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,76 @@
|
||||
using System; |
||||
using System.Buffers; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using StabilityMatrix.Models; |
||||
|
||||
namespace StabilityMatrix.Helper; |
||||
|
||||
public static class FileTransfers |
||||
{ |
||||
/// <summary> |
||||
/// Determines suitable buffer size based on stream length. |
||||
/// </summary> |
||||
/// <param name="totalBytes"></param> |
||||
/// <returns></returns> |
||||
public static ulong GetBufferSize(ulong totalBytes) => totalBytes switch |
||||
{ |
||||
< Size.MiB => 8 * Size.KiB, |
||||
< 100 * Size.MiB => 16 * Size.KiB, |
||||
< 500 * Size.MiB => Size.MiB, |
||||
< Size.GiB => 16 * Size.MiB, |
||||
_ => 32 * Size.MiB |
||||
}; |
||||
|
||||
public static async Task CopyFiles(Dictionary<string, string> files, IProgress<ProgressReport>? fileProgress = default, IProgress<ProgressReport>? totalProgress = default) |
||||
{ |
||||
var totalFiles = files.Count; |
||||
var currentFiles = 0; |
||||
var totalSize = Convert.ToUInt64(files.Keys.Select(x => new FileInfo(x).Length).Sum()); |
||||
var totalRead = 0ul; |
||||
|
||||
foreach(var (sourcePath, destPath) in files) |
||||
{ |
||||
var totalReadForFile = 0ul; |
||||
|
||||
await using var outStream = new FileStream(destPath, FileMode.Create, FileAccess.Write, FileShare.Read); |
||||
await using var inStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read); |
||||
var fileSize = (ulong) inStream.Length; |
||||
var fileName = Path.GetFileName(sourcePath); |
||||
currentFiles++; |
||||
await CopyStream(inStream , outStream, fileReadBytes => |
||||
{ |
||||
var lastRead = totalReadForFile; |
||||
totalReadForFile = Convert.ToUInt64(fileReadBytes); |
||||
totalRead += totalReadForFile - lastRead; |
||||
fileProgress?.Report(new ProgressReport(totalReadForFile, fileSize, fileName, $"{currentFiles}/{totalFiles}")); |
||||
totalProgress?.Report(new ProgressReport(totalRead, totalSize, fileName, $"{currentFiles}/{totalFiles}")); |
||||
} ); |
||||
} |
||||
} |
||||
|
||||
private static async Task CopyStream(Stream from, Stream to, Action<long> progress) |
||||
{ |
||||
var shared = ArrayPool<byte>.Shared; |
||||
var bufferSize = (int) GetBufferSize((ulong) from.Length); |
||||
var buffer = shared.Rent(bufferSize); |
||||
var totalRead = 0L; |
||||
|
||||
try |
||||
{ |
||||
while (totalRead < from.Length) |
||||
{ |
||||
var read = await from.ReadAsync(buffer.AsMemory(0, bufferSize)); |
||||
await to.WriteAsync(buffer.AsMemory(0, read)); |
||||
totalRead += read; |
||||
progress(totalRead); |
||||
} |
||||
} |
||||
finally |
||||
{ |
||||
shared.Return(buffer); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,13 @@
|
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
public enum CivitCommercialUse |
||||
{ |
||||
None, |
||||
Image, |
||||
Rent, |
||||
Sell |
||||
} |
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitCreator |
||||
{ |
||||
[JsonPropertyName("username")] |
||||
public string Username { get; set; } |
||||
|
||||
[JsonPropertyName("image")] |
||||
public string? Image { get; set; } |
||||
} |
@ -0,0 +1,31 @@
|
||||
using System; |
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitFile |
||||
{ |
||||
[JsonPropertyName("sizeKb")] |
||||
public double SizeKb { get; set; } |
||||
|
||||
[JsonPropertyName("pickleScanResult")] |
||||
public string PickleScanResult { get; set; } |
||||
|
||||
[JsonPropertyName("virusScanResult")] |
||||
public string VirusScanResult { get; set; } |
||||
|
||||
[JsonPropertyName("scannedAt")] |
||||
public DateTime? ScannedAt { get; set; } |
||||
|
||||
[JsonPropertyName("metadata")] |
||||
public CivitFileMetadata Metadata { get; set; } |
||||
|
||||
[JsonPropertyName("name")] |
||||
public string Name { get; set; } |
||||
|
||||
[JsonPropertyName("downloadUrl")] |
||||
public string DownloadUrl { get; set; } |
||||
|
||||
[JsonPropertyName("hashes")] |
||||
public CivitFileHashes Hashes { get; set; } |
||||
} |
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitFileHashes |
||||
{ |
||||
public string? SHA256 { get; set; } |
||||
|
||||
public string? CRC32 { get; set; } |
||||
|
||||
public string? BLAKE3 { get; set; } |
||||
} |
@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitFileMetadata |
||||
{ |
||||
[JsonPropertyName("fp")] |
||||
public CivitModelFpType? Fp { get; set; } |
||||
|
||||
[JsonPropertyName("size")] |
||||
public CivitModelSize? Size { get; set; } |
||||
|
||||
[JsonPropertyName("format")] |
||||
public CivitModelFormat? Format { get; set; } |
||||
} |
@ -0,0 +1,23 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitImage |
||||
{ |
||||
[JsonPropertyName("url")] |
||||
public string Url { get; set; } |
||||
|
||||
[JsonPropertyName("nsfw")] |
||||
public string Nsfw { get; set; } |
||||
|
||||
[JsonPropertyName("width")] |
||||
public int Width { get; set; } |
||||
|
||||
[JsonPropertyName("height")] |
||||
public int Height { get; set; } |
||||
|
||||
[JsonPropertyName("hash")] |
||||
public string Hash { get; set; } |
||||
|
||||
// TODO: "meta" ( object? ) |
||||
} |
@ -0,0 +1,25 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.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; } |
||||
} |
@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
public enum CivitMode |
||||
{ |
||||
Archived, |
||||
TakenDown |
||||
} |
@ -0,0 +1,36 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitModel |
||||
{ |
||||
[JsonPropertyName("id")] |
||||
public int Id { get; set; } |
||||
|
||||
[JsonPropertyName("name")] |
||||
public string Name { get; set; } |
||||
|
||||
[JsonPropertyName("description")] |
||||
public string Description { get; set; } |
||||
|
||||
[JsonPropertyName("type")] |
||||
public CivitModelType Type { get; set; } |
||||
|
||||
[JsonPropertyName("nsfw")] |
||||
public bool Nsfw { get; set; } |
||||
|
||||
[JsonPropertyName("tags")] |
||||
public string[] Tags { get; set; } |
||||
|
||||
[JsonPropertyName("mode")] |
||||
public CivitMode? Mode { get; set; } |
||||
|
||||
[JsonPropertyName("creator")] |
||||
public CivitCreator Creator { get; set; } |
||||
|
||||
[JsonPropertyName("stats")] |
||||
public CivitModelStats Stats { get; set; } |
||||
|
||||
[JsonPropertyName("modelVersions")] |
||||
public CivitModelVersion[] ModelVersions { get; set; } |
||||
} |
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
public enum CivitModelFormat |
||||
{ |
||||
SafeTensor, |
||||
PickleTensor, |
||||
Other |
||||
} |
@ -0,0 +1,13 @@
|
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
[SuppressMessage("ReSharper", "InconsistentNaming")] |
||||
public enum CivitModelFpType |
||||
{ |
||||
fp16, |
||||
fp32 |
||||
} |
@ -0,0 +1,12 @@
|
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
[SuppressMessage("ReSharper", "InconsistentNaming")] |
||||
public enum CivitModelSize |
||||
{ |
||||
full, |
||||
pruned, |
||||
} |
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitModelStats : CivitStats |
||||
{ |
||||
[JsonPropertyName("favoriteCount")] |
||||
public int FavoriteCount { get; set; } |
||||
|
||||
[JsonPropertyName("commentCount")] |
||||
public int CommentCount { get; set; } |
||||
} |
@ -0,0 +1,28 @@
|
||||
using System; |
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.Text.Json.Serialization; |
||||
using StabilityMatrix.Extensions; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
[SuppressMessage("ReSharper", "InconsistentNaming")] |
||||
public enum CivitModelType |
||||
{ |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.StableDiffusion)] |
||||
Checkpoint, |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.TextualInversion)] |
||||
TextualInversion, |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.Hypernetwork)] |
||||
Hypernetwork, |
||||
AestheticGradient, |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.Lora)] |
||||
LORA, |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)] |
||||
Controlnet, |
||||
Poses, |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.StableDiffusion)] |
||||
Model, |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.LyCORIS)] |
||||
LoCon |
||||
} |
@ -0,0 +1,37 @@
|
||||
using System; |
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
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 CivitFile[] Files { get; set; } |
||||
|
||||
[JsonPropertyName("images")] |
||||
public CivitImage[] Images { get; set; } |
||||
|
||||
[JsonPropertyName("stats")] |
||||
public CivitModelStats Stats { get; set; } |
||||
} |
@ -0,0 +1,106 @@
|
||||
using System.Text.Json.Serialization; |
||||
using Refit; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
|
||||
public class CivitModelsRequest |
||||
{ |
||||
/// <summary> |
||||
/// The number of results to be returned per page. This can be a number between 1 and 200. By default, each page will return 100 results |
||||
/// </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; } |
||||
} |
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitModelsResponse |
||||
{ |
||||
[JsonPropertyName("items")] |
||||
public CivitModel[]? Items { get; set; } |
||||
|
||||
[JsonPropertyName("metadata")] |
||||
public CivitMetadata? Metadata { get; set; } |
||||
} |
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
public enum CivitPeriod |
||||
{ |
||||
AllTime, |
||||
Year, |
||||
Month, |
||||
Week, |
||||
Day |
||||
} |
@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.Runtime.Serialization; |
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||
public enum CivitSortMode |
||||
{ |
||||
[EnumMember(Value = "Highest Rated")] |
||||
HighestRated, |
||||
[EnumMember(Value = "Most Downloaded")] |
||||
MostDownloaded, |
||||
[EnumMember(Value = "Newest")] |
||||
Newest |
||||
} |
@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization; |
||||
|
||||
namespace StabilityMatrix.Models.Api; |
||||
|
||||
public class CivitStats |
||||
{ |
||||
[JsonPropertyName("downloadCount")] |
||||
public int DownloadCount { get; set; } |
||||
|
||||
[JsonPropertyName("ratingCount")] |
||||
public int RatingCount { get; set; } |
||||
|
||||
[JsonPropertyName("rating")] |
||||
public double Rating { get; set; } |
||||
} |
@ -0,0 +1,49 @@
|
||||
using System; |
||||
using System.Text.Json; |
||||
using StabilityMatrix.Extensions; |
||||
using StabilityMatrix.Models.Api; |
||||
|
||||
namespace StabilityMatrix.Models; |
||||
|
||||
public class ConnectedModelInfo |
||||
{ |
||||
public int ModelId { get; set; } |
||||
public string ModelName { get; set; } |
||||
public string ModelDescription { get; set; } |
||||
public bool Nsfw { get; set; } |
||||
public string[] Tags { get; set; } |
||||
public CivitModelType ModelType { get; set; } |
||||
public int VersionId { get; set; } |
||||
public string VersionName { get; set; } |
||||
public string VersionDescription { get; set; } |
||||
public string? BaseModel { get; set; } |
||||
public CivitFileMetadata FileMetadata { get; set; } |
||||
public DateTime ImportedAt { get; set; } |
||||
public CivitFileHashes Hashes { get; set; } |
||||
|
||||
// User settings |
||||
public string? UserTitle { get; set; } |
||||
public string? ThumbnailImageUrl { get; set; } |
||||
|
||||
public ConnectedModelInfo(CivitModel civitModel, CivitModelVersion civitModelVersion, CivitFile civitFile, DateTime importedAt) |
||||
{ |
||||
ModelId = civitModel.Id; |
||||
ModelName = civitModel.Name; |
||||
ModelDescription = civitModel.Description; |
||||
Nsfw = civitModel.Nsfw; |
||||
Tags = civitModel.Tags; |
||||
ModelType = civitModel.Type; |
||||
VersionId = civitModelVersion.Id; |
||||
VersionName = civitModelVersion.Name; |
||||
VersionDescription = civitModelVersion.Description; |
||||
ImportedAt = importedAt; |
||||
BaseModel = civitModelVersion.BaseModel; |
||||
FileMetadata = civitFile.Metadata; |
||||
Hashes = civitFile.Hashes; |
||||
} |
||||
|
||||
public static ConnectedModelInfo? FromJson(string json) |
||||
{ |
||||
return JsonSerializer.Deserialize<ConnectedModelInfo>(json); |
||||
} |
||||
} |
@ -0,0 +1,98 @@
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Threading.Tasks; |
||||
using System.Windows; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using Microsoft.Extensions.Logging; |
||||
using StabilityMatrix.Extensions; |
||||
using StabilityMatrix.Helper; |
||||
using StabilityMatrix.Models; |
||||
using StabilityMatrix.Models.Api; |
||||
using StabilityMatrix.Services; |
||||
|
||||
namespace StabilityMatrix.ViewModels; |
||||
|
||||
public partial class CheckpointBrowserCardViewModel : ProgressViewModel |
||||
{ |
||||
private readonly IDownloadService downloadService; |
||||
private readonly ISnackbarService snackbarService; |
||||
public CivitModel CivitModel { get; init; } |
||||
|
||||
public override Visibility ProgressVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed; |
||||
public override Visibility TextVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed; |
||||
|
||||
public CheckpointBrowserCardViewModel(CivitModel civitModel, IDownloadService downloadService, ISnackbarService snackbarService) |
||||
{ |
||||
this.downloadService = downloadService; |
||||
this.snackbarService = snackbarService; |
||||
CivitModel = civitModel; |
||||
} |
||||
|
||||
[RelayCommand] |
||||
private void OpenModel() |
||||
{ |
||||
Process.Start(new ProcessStartInfo |
||||
{ |
||||
FileName = $"https://civitai.com/models/{CivitModel.Id}", |
||||
UseShellExecute = true |
||||
}); |
||||
} |
||||
|
||||
[RelayCommand] |
||||
private async Task Import(CivitModel model) |
||||
{ |
||||
Text = "Downloading..."; |
||||
|
||||
var latestModelFile = model.ModelVersions[0].Files[0]; |
||||
var fileExpectedSha256 = latestModelFile.Hashes.SHA256; |
||||
|
||||
var downloadPath = Path.Combine(SharedFolders.SharedFoldersPath, |
||||
model.Type.ConvertTo<SharedFolderType>().GetStringValue(), latestModelFile.Name); |
||||
|
||||
var downloadProgress = new Progress<ProgressReport>(progress => |
||||
{ |
||||
Value = progress.Percentage; |
||||
Text = $"Importing... {progress.Percentage}%"; |
||||
}); |
||||
await downloadService.DownloadToFileAsync(latestModelFile.DownloadUrl, downloadPath, progress: downloadProgress); |
||||
|
||||
// When sha256 is available, validate the downloaded file |
||||
if (!string.IsNullOrEmpty(fileExpectedSha256)) |
||||
{ |
||||
var hashProgress = new Progress<ProgressReport>(progress => |
||||
{ |
||||
Value = progress.Percentage; |
||||
Text = $"Validating... {progress.Percentage}%"; |
||||
}); |
||||
var sha256 = await FileHash.GetSha256Async(downloadPath, hashProgress); |
||||
if (sha256 != fileExpectedSha256.ToLowerInvariant()) |
||||
{ |
||||
Text = "Import Failed!"; |
||||
DelayedClearProgress(TimeSpan.FromSeconds(800)); |
||||
await snackbarService.ShowSnackbarAsync( |
||||
"This may be caused by network or server issues from CivitAI, please try again in a few minutes.", |
||||
"Download failed hash validation", LogLevel.Warning); |
||||
return; |
||||
} |
||||
else |
||||
{ |
||||
snackbarService.ShowSnackbarAsync($"{model.Type} {model.Name} imported successfully!", |
||||
"Import complete", LogLevel.Trace); |
||||
} |
||||
} |
||||
|
||||
Text = "Import complete!"; |
||||
Value = 100; |
||||
DelayedClearProgress(TimeSpan.FromMilliseconds(800)); |
||||
} |
||||
|
||||
private void DelayedClearProgress(TimeSpan delay) |
||||
{ |
||||
Task.Delay(delay).ContinueWith(_ => |
||||
{ |
||||
Text = string.Empty; |
||||
Value = 0; |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,129 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using NLog; |
||||
using StabilityMatrix.Api; |
||||
using StabilityMatrix.Helper; |
||||
using StabilityMatrix.Models.Api; |
||||
using StabilityMatrix.Services; |
||||
|
||||
namespace StabilityMatrix.ViewModels; |
||||
|
||||
public partial class CheckpointBrowserViewModel : ObservableObject |
||||
{ |
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
||||
private readonly ICivitApi civitApi; |
||||
private readonly IDownloadService downloadService; |
||||
private readonly ISnackbarService snackbarService; |
||||
private const int MaxModelsPerPage = 14; |
||||
|
||||
[ObservableProperty] private string? searchQuery; |
||||
[ObservableProperty] private ObservableCollection<CheckpointBrowserCardViewModel>? modelCards; |
||||
[ObservableProperty] private bool showNsfw; |
||||
[ObservableProperty] private bool showMainLoadingSpinner; |
||||
[ObservableProperty] private CivitPeriod selectedPeriod; |
||||
[ObservableProperty] private CivitSortMode sortMode; |
||||
[ObservableProperty] private int currentPageNumber; |
||||
[ObservableProperty] private int totalPages; |
||||
[ObservableProperty] private bool hasSearched; |
||||
[ObservableProperty] private bool canGoToNextPage; |
||||
[ObservableProperty] private bool canGoToPreviousPage; |
||||
[ObservableProperty] private bool isIndeterminate; |
||||
|
||||
public IEnumerable<CivitPeriod> AllCivitPeriods => Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>(); |
||||
public IEnumerable<CivitSortMode> AllSortModes => Enum.GetValues(typeof(CivitSortMode)).Cast<CivitSortMode>(); |
||||
|
||||
public CheckpointBrowserViewModel(ICivitApi civitApi, IDownloadService downloadService, ISnackbarService snackbarService) |
||||
{ |
||||
this.civitApi = civitApi; |
||||
this.downloadService = downloadService; |
||||
this.snackbarService = snackbarService; |
||||
|
||||
SelectedPeriod = CivitPeriod.Month; |
||||
SortMode = CivitSortMode.HighestRated; |
||||
HasSearched = false; |
||||
CurrentPageNumber = 1; |
||||
CanGoToPreviousPage = false; |
||||
CanGoToNextPage = true; |
||||
} |
||||
|
||||
[RelayCommand] |
||||
private async Task SearchModels() |
||||
{ |
||||
if (string.IsNullOrWhiteSpace(SearchQuery)) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
ShowMainLoadingSpinner = true; |
||||
|
||||
var models = await civitApi.GetModels(new CivitModelsRequest |
||||
{ |
||||
Query = SearchQuery, |
||||
Limit = MaxModelsPerPage, |
||||
Nsfw = ShowNsfw.ToString().ToLower(), |
||||
Sort = SortMode, |
||||
Period = SelectedPeriod, |
||||
Page = CurrentPageNumber |
||||
}); |
||||
|
||||
HasSearched = true; |
||||
TotalPages = models.Metadata.TotalPages; |
||||
CanGoToPreviousPage = CurrentPageNumber > 1; |
||||
CanGoToNextPage = CurrentPageNumber < TotalPages; |
||||
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(models.Items.Select( |
||||
m => new CheckpointBrowserCardViewModel(m, downloadService, snackbarService))); |
||||
ShowMainLoadingSpinner = false; |
||||
|
||||
Logger.Debug($"Found {models.Items.Length} models"); |
||||
} |
||||
|
||||
[RelayCommand] |
||||
private async Task PreviousPage() |
||||
{ |
||||
if (CurrentPageNumber == 1) return; |
||||
|
||||
CurrentPageNumber--; |
||||
await TrySearchAgain(false); |
||||
} |
||||
|
||||
[RelayCommand] |
||||
private async Task NextPage() |
||||
{ |
||||
CurrentPageNumber++; |
||||
await TrySearchAgain(false); |
||||
} |
||||
|
||||
partial void OnShowNsfwChanged(bool oldValue, bool newValue) |
||||
{ |
||||
TrySearchAgain(); |
||||
} |
||||
|
||||
partial void OnSelectedPeriodChanged(CivitPeriod oldValue, CivitPeriod newValue) |
||||
{ |
||||
TrySearchAgain(); |
||||
} |
||||
|
||||
partial void OnSortModeChanged(CivitSortMode oldValue, CivitSortMode newValue) |
||||
{ |
||||
TrySearchAgain(); |
||||
} |
||||
|
||||
private async Task TrySearchAgain(bool shouldUpdatePageNumber = true) |
||||
{ |
||||
if (!hasSearched) return; |
||||
ModelCards?.Clear(); |
||||
|
||||
if (shouldUpdatePageNumber) |
||||
{ |
||||
CurrentPageNumber = 1; |
||||
} |
||||
|
||||
// execute command instead of calling method directly so that the IsRunning property gets updated |
||||
await SearchModelsCommand.ExecuteAsync(null); |
||||
} |
||||
} |
@ -0,0 +1,28 @@
|
||||
using System.Windows; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
|
||||
namespace StabilityMatrix.ViewModels; |
||||
|
||||
/// <summary> |
||||
/// Generic view model for progress reporting. |
||||
/// </summary> |
||||
public partial class ProgressViewModel : ObservableObject |
||||
{ |
||||
[ObservableProperty] |
||||
[NotifyPropertyChangedFor(nameof(TextVisibility))] |
||||
private string text; |
||||
|
||||
[ObservableProperty] |
||||
private double value; |
||||
|
||||
[ObservableProperty] |
||||
private bool isIndeterminate; |
||||
|
||||
[ObservableProperty] |
||||
[NotifyPropertyChangedFor(nameof(ProgressVisibility))] |
||||
private bool isProgressVisible; |
||||
|
||||
public virtual Visibility ProgressVisibility => IsProgressVisible ? Visibility.Visible : Visibility.Collapsed; |
||||
|
||||
public virtual Visibility TextVisibility => string.IsNullOrEmpty(Text) ? Visibility.Collapsed : Visibility.Visible; |
||||
} |
Loading…
Reference in new issue