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"
x:Class="StabilityMatrix.CheckpointManagerPage"
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:designData="clr-namespace:StabilityMatrix.DesignData"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
@ -19,6 +20,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<converters:ValueConverterGroup x:Key="InvertAndVisibleOrHidden">
<converters:BoolNegationConverter />
<converters:BooleanToHiddenVisibleConverter />
</converters:ValueConverterGroup>
<DropShadowEffect
BlurRadius="8"
Color="#FF000000"
@ -28,36 +34,103 @@
x:Key="TextDropShadowEffect" />
<DataTemplate DataType="{x:Type models:CheckpointFile}" x:Key="CheckpointFileDataTemplate">
<ui:CardAction Height="96" Width="240">
<ui:CardAction.ContextMenu>
<ui:Card
Height="96"
Margin="8"
Width="240">
<ui:Card.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" />
<MenuItem Command="{Binding DeleteCommand}" Header="Delete" />
</ContextMenu>
</ui:CardAction.ContextMenu>
<StackPanel Orientation="Vertical">
<ui:Image
CornerRadius="4"
Margin="4,4,4,8"
MinHeight="256"
MinWidth="200"
Source="{Binding PreviewImage}"
Stretch="UniformToFill"
Visibility="Collapsed"
Width="128" />
<TextBlock
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
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>
</ui:CardAction>
</ui:Card.ContextMenu>
<Grid>
<!-- Main contents, hidden when IsLoading is true -->
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="{Binding IsLoading, Converter={StaticResource InvertAndVisibleOrHidden}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.2*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<ui:Image
CornerRadius="4"
Margin="4,4,4,8"
MinHeight="256"
MinWidth="200"
Source="{Binding PreviewImage}"
Stretch="UniformToFill"
Visibility="Collapsed"
Width="128" />
<TextBlock
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
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 DataType="{x:Type models:CheckpointFolder}" x:Key="CheckpointFolderGridDataTemplate">
@ -173,7 +246,7 @@
HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource CheckpointFolderGridDataTemplate}"
ItemsSource="{Binding CheckpointFolders, Mode=OneWay}"
Margin="16,16,16,16" />
Margin="8" />
</StackPanel>
</Grid>
</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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NLog;
namespace StabilityMatrix.Models;
public partial class CheckpointFile : ObservableObject
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
// Event for when this file is deleted
public event EventHandler<CheckpointFile>? Deleted;
/// <summary>
/// Absolute path to the checkpoint file.
/// </summary>
public string FilePath { get; init; } = string.Empty;
/// <summary>
/// Custom title for UI.
/// </summary>
public string Title { get; init; } = string.Empty;
public string? PreviewImagePath { get; set; }
[ObservableProperty] private string title = string.Empty;
public string? PreviewImagePath { get; set; }
public BitmapImage? PreviewImage { get; set; }
public bool IsPreviewImageLoaded => PreviewImage != null;
[ObservableProperty] private ConnectedModelInfo? connectedModel;
public bool IsConnectedModel => ConnectedModel != null;
[ObservableProperty] private bool isLoading;
public string FileName => Path.GetFileName(FilePath);
private static readonly string[] SupportedCheckpointExtensions = { ".safetensors", ".pt", ".ckpt", ".pth" };
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]
public void Delete()
private async Task DeleteAsync()
{
if (File.Exists(FilePath))
{
Task.Run(() =>
// Start progress ring
IsLoading = true;
var timer = Stopwatch.StartNew();
try
{
File.Delete(FilePath);
Deleted?.Invoke(this, this);
});
}
if (PreviewImagePath != null && File.Exists(PreviewImagePath))
{
Task.Run(() => File.Delete(PreviewImagePath));
await Task.Run(() => File.Delete(FilePath));
if (PreviewImagePath != null && File.Exists(PreviewImagePath))
{
await Task.Run(() => File.Delete(PreviewImagePath));
}
// If it was too fast, wait a bit to show progress ring
var targetDelay = new Random().Next(200, 500);
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>
/// Indexes directory and yields all checkpoint files.
/// First we match all files with supported extensions.
/// If found, we also look for
/// - {filename}.preview.{image-extensions}
/// - {filename}.preview.{image-extensions} (preview image)
/// - {filename}.cm-info.json (connected model info)
/// </summary>
public static IEnumerable<CheckpointFile> FromDirectoryIndex(string directory, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
@ -75,6 +104,22 @@ public partial class CheckpointFile : ObservableObject
Title = Path.GetFileNameWithoutExtension(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
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