From 6c7c6ead561c2e1cd1d82337ec8d607333a3f7f2 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 9 Feb 2024 23:27:27 -0800 Subject: [PATCH] Added the ability to choose where CivitAI model downloads are saved --- CHANGELOG.md | 1 + .../CheckpointBrowserCardViewModel.cs | 37 +++---- .../Dialogs/SelectModelVersionViewModel.cs | 99 +++++++++++++++---- .../Dialogs/SelectModelVersionDialog.axaml | 53 ++++++++-- 4 files changed, 148 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84b51d1b..5a7e2e39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI - Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel - Added extension management for SD.Next and Stable Diffusion WebUI-UX +- Added the ability to choose where CivitAI model downloads are saved ## v2.8.3 ### Fixed diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs index afdeb085..148712c0 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs @@ -190,13 +190,6 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel IsFavorite = settingsManager.Settings.FavoriteModels.Contains(CivitModel.Id); } - [RelayCommand] - private async Task Import(CivitModel model) - { - await DoImport(model); - CheckIfInstalled(); - } - [RelayCommand] private async Task ShowVersionDialog(CivitModel model) { @@ -220,7 +213,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel IsSecondaryButtonEnabled = false, IsFooterVisible = false, MaxDialogWidth = 750, - MaxDialogHeight = 850, + MaxDialogHeight = 950, }; var prunedDescription = PruneDescription(model); @@ -229,6 +222,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel viewModel.Dialog = dialog; viewModel.Title = model.Name; viewModel.Description = prunedDescription; + viewModel.CivitModel = model; viewModel.Versions = versions .Select( version => @@ -252,8 +246,21 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel var selectedVersion = viewModel?.SelectedVersionViewModel?.ModelVersion; var selectedFile = viewModel?.SelectedFile?.CivitFile; + DirectoryPath downloadPath; + if (viewModel?.IsCustomSelected is true) + { + downloadPath = viewModel.CustomInstallLocation; + } + else + { + var subFolder = + viewModel?.SelectedInstallLocation + ?? Path.Combine("Models", model.Type.ConvertTo().GetStringValue()); + downloadPath = Path.Combine(settingsManager.LibraryDir, subFolder); + } + await Task.Delay(100); - await DoImport(model, selectedVersion, selectedFile); + await DoImport(model, downloadPath, selectedVersion, selectedFile); } private static string PruneDescription(CivitModel model) @@ -322,6 +329,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel private async Task DoImport( CivitModel model, + DirectoryPath downloadFolder, CivitModelVersion? selectedVersion = null, CivitFile? selectedFile = null ) @@ -362,18 +370,13 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel return; } - var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory); - - var downloadDirectory = rootModelsDirectory.JoinDir( - model.Type.ConvertTo().GetStringValue() - ); // Folders might be missing if user didn't install any packages yet - downloadDirectory.Create(); + downloadFolder.Create(); - var downloadPath = downloadDirectory.JoinFile(modelFile.Name); + var downloadPath = downloadFolder.JoinFile(modelFile.Name); // Download model info and preview first - var cmInfoPath = await SaveCmInfo(model, modelVersion, modelFile, downloadDirectory); + var cmInfoPath = await SaveCmInfo(model, modelVersion, modelFile, downloadFolder); var previewImagePath = await SavePreviewImage(modelVersion, downloadPath); // Create tracked download diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs index ef7c2ddf..66e75071 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Controls.Notifications; using Avalonia.Media.Imaging; +using Avalonia.Platform.Storage; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using FluentAvalonia.UI.Controls; @@ -15,7 +16,10 @@ using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.Api; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Services; @@ -23,17 +27,20 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; [ManagedService] [Transient] -public partial class SelectModelVersionViewModel : ContentDialogViewModelBase +public partial class SelectModelVersionViewModel( + ISettingsManager settingsManager, + IDownloadService downloadService, + IModelIndexService modelIndexService, + INotificationService notificationService +) : ContentDialogViewModelBase { - private readonly ISettingsManager settingsManager; - private readonly IDownloadService downloadService; - private readonly IModelIndexService modelIndexService; - private readonly INotificationService notificationService; + private readonly IDownloadService downloadService = downloadService; public required ContentDialog Dialog { get; set; } public required IReadOnlyList Versions { get; set; } public required string Description { get; set; } public required string Title { get; set; } + public required CivitModel CivitModel { get; set; } [ObservableProperty] private Bitmap? previewImage; @@ -63,25 +70,25 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase [ObservableProperty] private string importTooltip = string.Empty; - public int DisplayedPageNumber => SelectedImageIndex + 1; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsCustomSelected))] + private string selectedInstallLocation = string.Empty; - public SelectModelVersionViewModel( - ISettingsManager settingsManager, - IDownloadService downloadService, - IModelIndexService modelIndexService, - INotificationService notificationService - ) - { - this.settingsManager = settingsManager; - this.downloadService = downloadService; - this.modelIndexService = modelIndexService; - this.notificationService = notificationService; - } + [ObservableProperty] + private ObservableCollection availableInstallLocations = []; + + [ObservableProperty] + private string customInstallLocation = string.Empty; + + public bool IsCustomSelected => SelectedInstallLocation == "Custom..."; + + public int DisplayedPageNumber => SelectedImageIndex + 1; public override void OnLoaded() { SelectedVersionViewModel = Versions[0]; CanGoToNextImage = true; + LoadInstallLocations(); } partial void OnSelectedVersionViewModelChanged(ModelVersionViewModel? value) @@ -235,6 +242,18 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase } } + partial void OnSelectedInstallLocationChanged(string value) + { + if (value.Equals("Custom...", StringComparison.OrdinalIgnoreCase)) + { + Dispatcher.UIThread.InvokeAsync(SelectCustomFolder); + } + else + { + CustomInstallLocation = string.Empty; + } + } + public void PreviousImage() { if (SelectedImageIndex > 0) @@ -250,4 +269,48 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase CanGoToPreviousImage = SelectedImageIndex > 0; CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1; } + + public async Task SelectCustomFolder() + { + var files = await App.StorageProvider.OpenFolderPickerAsync( + new FolderPickerOpenOptions + { + Title = "Select Download Folder", + AllowMultiple = false, + SuggestedStartLocation = await App.StorageProvider.TryGetFolderFromPathAsync( + Path.Combine( + settingsManager.ModelsDirectory, + CivitModel.Type.ConvertTo().GetStringValue() + ) + ) + } + ); + + if (files.FirstOrDefault()?.TryGetLocalPath() is { } path) + { + CustomInstallLocation = path; + } + } + + private void LoadInstallLocations() + { + var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory); + var downloadDirectory = rootModelsDirectory.JoinDir( + CivitModel.Type.ConvertTo().GetStringValue() + ); + var installLocations = new ObservableCollection + { + downloadDirectory.ToString().Replace(rootModelsDirectory, "Models") + }; + + foreach (var directory in downloadDirectory.EnumerateDirectories()) + { + installLocations.Add(directory.ToString().Replace(rootModelsDirectory, "Models")); + } + + installLocations.Add("Custom..."); + + AvailableInstallLocations = installLocations; + SelectedInstallLocation = installLocations.FirstOrDefault(); + } } diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml index e53b82f0..8edc10b8 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml @@ -21,7 +21,7 @@ Margin="8" MinHeight="450" MinWidth="700" - RowDefinitions="Auto, *, Auto" + RowDefinitions="Auto, Auto, *, Auto" ColumnDefinitions="*,Auto,*"> @@ -86,8 +86,12 @@ + - + + @@ -179,20 +190,48 @@ + + + + + + + + + + Margin="8, 8"> - -