Multi-Platform Package Manager for Stable Diffusion
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

371 lines
12 KiB

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using NLog;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using Resources = StabilityMatrix.Avalonia.Languages.Resources;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;
using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(CheckpointsPage))]
[Singleton]
public partial class CheckpointsPageViewModel : PageViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly ISharedFolders sharedFolders;
private readonly ISettingsManager settingsManager;
private readonly ModelFinder modelFinder;
private readonly IDownloadService downloadService;
private readonly INotificationService notificationService;
private readonly IMetadataImportService metadataImportService;
public override string Title => Resources.Label_Checkpoints;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Notebook, IsFilled = true };
[ObservableProperty]
private ObservableCollection<string> baseModelOptions =
new(
Enum.GetValues<CivitBaseModelType>()
.Where(x => x != CivitBaseModelType.All)
.Select(x => x.GetStringValue())
);
[ObservableProperty]
private ObservableCollection<string> selectedBaseModels = [];
// Toggle button for auto hashing new drag-and-dropped files for connected upgrade
[ObservableProperty]
private bool isImportAsConnected;
[ObservableProperty]
private bool isLoading;
[ObservableProperty]
private bool isIndexing;
[ObservableProperty]
private bool showConnectedModelImages;
[ObservableProperty]
private string searchFilter = string.Empty;
[ObservableProperty]
private bool isCategoryTipOpen;
[ObservableProperty]
private ProgressReport progress;
partial void OnIsImportAsConnectedChanged(bool value)
{
if (settingsManager.IsLibraryDirSet && value != settingsManager.Settings.IsImportAsConnected)
{
settingsManager.Transaction(s => s.IsImportAsConnected = value);
}
}
public SourceCache<CheckpointFolder, string> CheckpointFoldersCache { get; } = new(x => x.DirectoryPath);
public IObservableCollection<CheckpointFolder> CheckpointFolders { get; } =
new ObservableCollectionExtended<CheckpointFolder>();
public IObservableCollection<CheckpointFolder> DisplayedCheckpointFolders { get; } =
new ObservableCollectionExtended<CheckpointFolder>();
public string ClearButtonText =>
SelectedBaseModels.Count == BaseModelOptions.Count
? Resources.Action_ClearSelection
: Resources.Action_SelectAll;
private bool isClearing = false;
public CheckpointsPageViewModel(
ISharedFolders sharedFolders,
ISettingsManager settingsManager,
IDownloadService downloadService,
INotificationService notificationService,
IMetadataImportService metadataImportService,
ModelFinder modelFinder
)
{
this.sharedFolders = sharedFolders;
this.settingsManager = settingsManager;
this.downloadService = downloadService;
this.notificationService = notificationService;
this.metadataImportService = metadataImportService;
this.modelFinder = modelFinder;
SelectedBaseModels = new ObservableCollection<string>(BaseModelOptions);
SelectedBaseModels.CollectionChanged += (_, _) =>
{
foreach (var folder in CheckpointFolders)
{
folder.BaseModelOptionsCache.EditDiff(SelectedBaseModels);
}
CheckpointFoldersCache.Refresh();
OnPropertyChanged(nameof(ClearButtonText));
if (!isClearing)
{
settingsManager.Transaction(
settings => settings.SelectedBaseModels = SelectedBaseModels.ToList()
);
}
};
CheckpointFoldersCache
.Connect()
.DeferUntilLoaded()
.Bind(CheckpointFolders)
.Filter(ContainsSearchFilter)
.Filter(ContainsBaseModel)
.SortBy(x => x.Title)
.Bind(DisplayedCheckpointFolders)
.Subscribe();
}
public override void OnLoaded()
{
base.OnLoaded();
var sw = Stopwatch.StartNew();
// Set UI states
IsImportAsConnected = settingsManager.Settings.IsImportAsConnected;
ShowConnectedModelImages = settingsManager.Settings.ShowConnectedModelImages;
// Refresh search filter
OnSearchFilterChanged(string.Empty);
if (Design.IsDesignMode)
return;
if (!settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.CheckpointCategoriesTip))
{
IsCategoryTipOpen = true;
settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.CheckpointCategoriesTip));
}
IsLoading = CheckpointFolders.Count == 0;
IsIndexing = CheckpointFolders.Count > 0;
IndexFolders();
IsLoading = false;
IsIndexing = false;
isClearing = true;
SelectedBaseModels.Clear();
isClearing = false;
SelectedBaseModels.AddRange(settingsManager.Settings.SelectedBaseModels);
Logger.Info($"OnLoadedAsync in {sw.ElapsedMilliseconds}ms");
}
public void ClearSearchQuery()
{
SearchFilter = string.Empty;
}
public void ClearOrSelectAllBaseModels()
{
if (SelectedBaseModels.Count == BaseModelOptions.Count)
{
SelectedBaseModels.Clear();
}
else
{
SelectedBaseModels.Clear();
SelectedBaseModels.AddRange(BaseModelOptions);
}
}
// ReSharper disable once UnusedParameterInPartialMethod
partial void OnSearchFilterChanged(string value)
{
foreach (var folder in CheckpointFolders)
{
folder.SearchFilter = value;
}
CheckpointFoldersCache.Refresh();
}
partial void OnShowConnectedModelImagesChanged(bool value)
{
if (settingsManager.IsLibraryDirSet && value != settingsManager.Settings.ShowConnectedModelImages)
{
settingsManager.Transaction(s => s.ShowConnectedModelImages = value);
}
}
private bool ContainsSearchFilter(CheckpointFolder folder)
{
ArgumentNullException.ThrowIfNull(folder);
if (string.IsNullOrWhiteSpace(SearchFilter))
{
return true;
}
// Check files in the current folder
return folder.CheckpointFiles.Any(
x =>
x.FileName.Contains(SearchFilter, StringComparison.OrdinalIgnoreCase)
10 months ago
|| x.Title.Contains(SearchFilter, StringComparison.OrdinalIgnoreCase)
|| x.ConnectedModel?.ModelName.Contains(SearchFilter, StringComparison.OrdinalIgnoreCase)
== true
|| x.ConnectedModel?.Tags.Any(
t => t.Contains(SearchFilter, StringComparison.OrdinalIgnoreCase)
) == true
|| x.ConnectedModel?.TrainedWordsString.Contains(
SearchFilter,
StringComparison.OrdinalIgnoreCase
) == true
)
||
// If no matching files were found in the current folder, check in all subfolders
folder.SubFolders.Any(ContainsSearchFilter);
}
private bool ContainsBaseModel(CheckpointFolder folder)
{
ArgumentNullException.ThrowIfNull(folder);
if (SelectedBaseModels.Count == 0 || SelectedBaseModels.Count == BaseModelOptions.Count)
return true;
if (!folder.DisplayedCheckpointFiles.Any())
return true;
return folder.CheckpointFiles.Any(
x =>
x.IsConnectedModel
? SelectedBaseModels.Contains(x.ConnectedModel?.BaseModel)
: SelectedBaseModels.Contains("Other")
) || folder.SubFolders.Any(ContainsBaseModel);
}
private void IndexFolders()
{
var modelsDirectory = settingsManager.ModelsDirectory;
// Setup shared folders in case they're missing
sharedFolders.SetupSharedModelFolders();
var folders = Directory.GetDirectories(modelsDirectory);
var sw = Stopwatch.StartNew();
var updatedFolders = new List<CheckpointFolder>();
// Index all folders
foreach (var folder in folders)
{
// Get from cache or create new
if (CheckpointFoldersCache.Lookup(folder) is { HasValue: true } result)
{
result.Value.Index();
updatedFolders.Add(result.Value);
}
else
{
var checkpointFolder = new CheckpointFolder(
settingsManager,
downloadService,
modelFinder,
notificationService,
metadataImportService
)
{
Title = Path.GetFileName(folder),
DirectoryPath = folder,
IsExpanded = true // Top level folders expanded by default
};
checkpointFolder.Index();
updatedFolders.Add(checkpointFolder);
}
}
CheckpointFoldersCache.EditDiff(updatedFolders, (a, b) => a.Title == b.Title);
sw.Stop();
Logger.Info($"IndexFolders in {sw.Elapsed.TotalMilliseconds:F1}ms");
}
[RelayCommand]
private async Task OpenModelsFolder()
{
await ProcessRunner.OpenFolderBrowser(settingsManager.ModelsDirectory);
}
[RelayCommand]
private async Task FindConnectedMetadata()
{
var progressHandler = new Progress<ProgressReport>(report =>
{
Progress = report;
});
await metadataImportService.ScanDirectoryForMissingInfo(
settingsManager.ModelsDirectory,
progressHandler
);
notificationService.Show(
"Scan Complete",
"Finished scanning for missing metadata.",
NotificationType.Success
);
DelayedClearProgress(TimeSpan.FromSeconds(1.5));
}
[RelayCommand]
private async Task UpdateExistingMetadata()
{
var progressHandler = new Progress<ProgressReport>(report =>
{
Progress = report;
});
await metadataImportService.UpdateExistingMetadata(settingsManager.ModelsDirectory, progressHandler);
notificationService.Show("Scan Complete", "Finished updating metadata.", NotificationType.Success);
DelayedClearProgress(TimeSpan.FromSeconds(1.5));
}
private void DelayedClearProgress(TimeSpan delay)
{
Task.Delay(delay)
.ContinueWith(_ =>
{
IsLoading = false;
Progress = new ProgressReport(0, 0);
});
}
}