Browse Source

Add ConnectedModelInfo, deletion progress

pull/5/head
Ionite 1 year ago
parent
commit
e11f3d36c3
No known key found for this signature in database
  1. 127
      StabilityMatrix/CheckpointManagerPage.xaml
  2. 32
      StabilityMatrix/Converters/BooleanToHiddenVisibleConverter.cs
  3. 79
      StabilityMatrix/Models/CheckpointFile.cs
  4. 49
      StabilityMatrix/Models/ConnectedModelInfo.cs

127
StabilityMatrix/CheckpointManagerPage.xaml

@ -9,6 +9,7 @@
mc:Ignorable="d" mc:Ignorable="d"
x:Class="StabilityMatrix.CheckpointManagerPage" x:Class="StabilityMatrix.CheckpointManagerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:converters="clr-namespace:StabilityMatrix.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:designData="clr-namespace:StabilityMatrix.DesignData" xmlns:designData="clr-namespace:StabilityMatrix.DesignData"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
@ -19,6 +20,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources> <Page.Resources>
<converters:ValueConverterGroup x:Key="InvertAndVisibleOrHidden">
<converters:BoolNegationConverter />
<converters:BooleanToHiddenVisibleConverter />
</converters:ValueConverterGroup>
<DropShadowEffect <DropShadowEffect
BlurRadius="8" BlurRadius="8"
Color="#FF000000" Color="#FF000000"
@ -28,36 +34,103 @@
x:Key="TextDropShadowEffect" /> x:Key="TextDropShadowEffect" />
<DataTemplate DataType="{x:Type models:CheckpointFile}" x:Key="CheckpointFileDataTemplate"> <DataTemplate DataType="{x:Type models:CheckpointFile}" x:Key="CheckpointFileDataTemplate">
<ui:CardAction Height="96" Width="240"> <ui:Card
<ui:CardAction.ContextMenu> Height="96"
Margin="8"
Width="240">
<ui:Card.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="Rename" /> <MenuItem Header="Rename" />
<MenuItem Command="{Binding DeleteCommand}" Header="Delete" /> <MenuItem Command="{Binding DeleteCommand}" Header="Delete" />
</ContextMenu> </ContextMenu>
</ui:CardAction.ContextMenu> </ui:Card.ContextMenu>
<StackPanel Orientation="Vertical"> <Grid>
<ui:Image <!-- Main contents, hidden when IsLoading is true -->
CornerRadius="4" <Grid
Margin="4,4,4,8" HorizontalAlignment="Stretch"
MinHeight="256" VerticalAlignment="Stretch"
MinWidth="200" Visibility="{Binding IsLoading, Converter={StaticResource InvertAndVisibleOrHidden}}">
Source="{Binding PreviewImage}" <Grid.ColumnDefinitions>
Stretch="UniformToFill" <ColumnDefinition Width="*" />
Visibility="Collapsed" <ColumnDefinition Width="0.2*" />
Width="128" /> </Grid.ColumnDefinitions>
<TextBlock <StackPanel Orientation="Vertical">
Foreground="{DynamicResource TextFillColorPrimaryBrush}" <ui:Image
Margin="0,0,0,0" CornerRadius="4"
Text="{Binding Title}" Margin="4,4,4,8"
VerticalAlignment="Center" /> MinHeight="256"
<TextBlock MinWidth="200"
FontSize="11" Source="{Binding PreviewImage}"
Foreground="{DynamicResource TextFillColorTertiaryBrush}" Stretch="UniformToFill"
Margin="0,2,0,0" Visibility="Collapsed"
Text="{Binding FileName}" Width="128" />
VerticalAlignment="Center" /> <TextBlock
</StackPanel> Foreground="{DynamicResource TextFillColorPrimaryBrush}"
</ui:CardAction> Margin="0,0,0,0"
Text="{Binding Title}"
VerticalAlignment="Center" />
<TextBlock
FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="0,2,0,0"
Text="{Binding FileName}"
VerticalAlignment="Center" />
</StackPanel>
<Grid Grid.Column="1">
<ui:Button
Background="Transparent"
BorderBrush="Transparent"
FontSize="20"
HorizontalAlignment="Right"
IsEnabled="False"
MaxHeight="48"
MaxWidth="64"
Padding="0"
VerticalAlignment="Top"
Visibility="{Binding IsConnectedModel, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
<ui:Button.Icon>
<ui:SymbolIcon
FontSize="12"
Foreground="LightGreen"
Symbol="CloudCheckmark24"
ToolTip="Connected Model" />
</ui:Button.Icon>
</ui:Button>
<ui:Button
Background="Transparent"
BorderBrush="Transparent"
FontSize="20"
HorizontalAlignment="Right"
IsEnabled="False"
MaxHeight="48"
MaxWidth="64"
Padding="0"
VerticalAlignment="Top"
Visibility="Collapsed">
<ui:Button.Icon>
<ui:SymbolIcon
FontSize="12"
Foreground="OrangeRed"
Symbol="CloudArrowUp24"
ToolTip="Update Available" />
</ui:Button.Icon>
</ui:Button>
</Grid>
</Grid>
<!-- Progress ring -->
<ui:ProgressRing
Grid.Row="0"
Height="32"
HorizontalAlignment="Center"
IsEnabled="{Binding IsLoading}"
IsIndeterminate="True"
Padding="0"
VerticalAlignment="Center"
Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"
Width="32" />
</Grid>
</ui:Card>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type models:CheckpointFolder}" x:Key="CheckpointFolderGridDataTemplate"> <DataTemplate DataType="{x:Type models:CheckpointFolder}" x:Key="CheckpointFolderGridDataTemplate">
@ -173,7 +246,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource CheckpointFolderGridDataTemplate}" ItemTemplate="{StaticResource CheckpointFolderGridDataTemplate}"
ItemsSource="{Binding CheckpointFolders, Mode=OneWay}" ItemsSource="{Binding CheckpointFolders, Mode=OneWay}"
Margin="16,16,16,16" /> Margin="8" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</ui:DynamicScrollViewer> </ui:DynamicScrollViewer>

32
StabilityMatrix/Converters/BooleanToHiddenVisibleConverter.cs

@ -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;
}
}

79
StabilityMatrix/Models/CheckpointFile.cs

@ -1,64 +1,93 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using NLog;
namespace StabilityMatrix.Models; namespace StabilityMatrix.Models;
public partial class CheckpointFile : ObservableObject public partial class CheckpointFile : ObservableObject
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
// Event for when this file is deleted // Event for when this file is deleted
public event EventHandler<CheckpointFile>? Deleted; public event EventHandler<CheckpointFile>? Deleted;
/// <summary> /// <summary>
/// Absolute path to the checkpoint file. /// Absolute path to the checkpoint file.
/// </summary> /// </summary>
public string FilePath { get; init; } = string.Empty; public string FilePath { get; init; } = string.Empty;
/// <summary> /// <summary>
/// Custom title for UI. /// Custom title for UI.
/// </summary> /// </summary>
public string Title { get; init; } = string.Empty; [ObservableProperty] private string title = string.Empty;
public string? PreviewImagePath { get; set; }
public string? PreviewImagePath { get; set; }
public BitmapImage? PreviewImage { get; set; } public BitmapImage? PreviewImage { get; set; }
public bool IsPreviewImageLoaded => PreviewImage != null; public bool IsPreviewImageLoaded => PreviewImage != null;
[ObservableProperty] private ConnectedModelInfo? connectedModel;
public bool IsConnectedModel => ConnectedModel != null;
[ObservableProperty] private bool isLoading;
public string FileName => Path.GetFileName(FilePath); public string FileName => Path.GetFileName(FilePath);
private static readonly string[] SupportedCheckpointExtensions = { ".safetensors", ".pt", ".ckpt", ".pth" }; private static readonly string[] SupportedCheckpointExtensions = { ".safetensors", ".pt", ".ckpt", ".pth" };
private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg" }; private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg" };
partial void OnConnectedModelChanged(ConnectedModelInfo? value)
{
if (value == null) return;
// Update title, first check user defined, then connected model name
Title = value.UserTitle ?? value.ModelName;
}
[RelayCommand] [RelayCommand]
public void Delete() private async Task DeleteAsync()
{ {
if (File.Exists(FilePath)) if (File.Exists(FilePath))
{ {
Task.Run(() => // Start progress ring
IsLoading = true;
var timer = Stopwatch.StartNew();
try
{ {
File.Delete(FilePath); await Task.Run(() => File.Delete(FilePath));
Deleted?.Invoke(this, this); if (PreviewImagePath != null && File.Exists(PreviewImagePath))
}); {
} await Task.Run(() => File.Delete(PreviewImagePath));
}
if (PreviewImagePath != null && File.Exists(PreviewImagePath)) // If it was too fast, wait a bit to show progress ring
{ var targetDelay = new Random().Next(200, 500);
Task.Run(() => File.Delete(PreviewImagePath)); var elapsed = timer.ElapsedMilliseconds;
if (elapsed < targetDelay)
{
await Task.Delay(targetDelay - (int) elapsed);
}
}
catch (IOException e)
{
Logger.Error(e, $"Failed to delete checkpoint file: {FilePath}");
IsLoading = false;
return; // Don't delete from collection
}
} }
Deleted?.Invoke(this, this);
} }
/// <summary> /// <summary>
/// Indexes directory and yields all checkpoint files. /// Indexes directory and yields all checkpoint files.
/// First we match all files with supported extensions. /// First we match all files with supported extensions.
/// If found, we also look for /// If found, we also look for
/// - {filename}.preview.{image-extensions} /// - {filename}.preview.{image-extensions} (preview image)
/// - {filename}.cm-info.json (connected model info)
/// </summary> /// </summary>
public static IEnumerable<CheckpointFile> FromDirectoryIndex(string directory, SearchOption searchOption = SearchOption.TopDirectoryOnly) public static IEnumerable<CheckpointFile> FromDirectoryIndex(string directory, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{ {
@ -75,6 +104,22 @@ public partial class CheckpointFile : ObservableObject
Title = Path.GetFileNameWithoutExtension(file), Title = Path.GetFileNameWithoutExtension(file),
FilePath = Path.Combine(directory, file), FilePath = Path.Combine(directory, file),
}; };
// Check for connected model info
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);
var cmInfoPath = $"{fileNameWithoutExtension}.cm-info.json";
if (files.ContainsKey(cmInfoPath))
{
try
{
var jsonData = File.ReadAllText(Path.Combine(directory, cmInfoPath));
checkpointFile.ConnectedModel = ConnectedModelInfo.FromJson(jsonData);
}
catch (IOException e)
{
Debug.WriteLine($"Failed to parse {cmInfoPath}: {e}");
}
}
// Check for preview image // Check for preview image
var previewImage = SupportedImageExtensions.Select(ext => $"{checkpointFile.FileName}.preview.{ext}").FirstOrDefault(files.ContainsKey); var previewImage = SupportedImageExtensions.Select(ext => $"{checkpointFile.FileName}.preview.{ext}").FirstOrDefault(files.ContainsKey);

49
StabilityMatrix/Models/ConnectedModelInfo.cs

@ -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);
}
}
Loading…
Cancel
Save