diff --git a/CHANGELOG.md b/CHANGELOG.md index d08547fd..c61ffeee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Added image and model details in model selection boxes - Added CLIP Skip setting, toggleable from the model settings button +## v2.7.6 +### Added +- Added SDXL Turbo and Stable Video Diffusion to the Hugging Face tab +### Changed +- ControlNet model selector will now show the parent directory of a model when relevant +### Fixed +- Fixed Python Packages dialog crash +- Fixed Base Model downloads from the Hugging Face tab downloading to the wrong folder + ## v2.7.5 ### Fixed - Fixed Python Packages manager crash when pip list returns warnings in json diff --git a/StabilityMatrix.Avalonia/Assets/hf-packages.json b/StabilityMatrix.Avalonia/Assets/hf-packages.json index 498e9c38..c0a12099 100644 --- a/StabilityMatrix.Avalonia/Assets/hf-packages.json +++ b/StabilityMatrix.Avalonia/Assets/hf-packages.json @@ -38,6 +38,26 @@ "LicenseType": "Open RAIL++", "LicensePath": "LICENSE.md" }, + { + "ModelCategory": "BaseModel", + "ModelName": "SDXL Turbo", + "RepositoryPath": "stabilityai/sdxl-turbo", + "Files": [ + "sd_xl_turbo_1.0_fp16.safetensors" + ], + "LicenseType": "SAI-NC-Community", + "LicensePath": "LICENSE.TXT" + }, + { + "ModelCategory": "BaseModel", + "ModelName": "Stable Video Diffusion", + "RepositoryPath": "stabilityai/stable-video-diffusion-img2vid", + "Files": [ + "svd.safetensors" + ], + "LicenseType": "Stable-Video-Diffusion-NC-Community", + "LicensePath": "LICENSE" + }, { "ModelCategory": "ControlNet", "ModelName": "Canny", diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj index 2113c78c..b9e5dd96 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj @@ -31,30 +31,30 @@ - - - - + + + + - + - + - - + + - - - + + + + - - - + + @@ -68,16 +68,16 @@ - - + + - + - - + + diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs index 1930907d..0f3804a0 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs @@ -41,7 +41,8 @@ public partial class HuggingFacePageViewModel : TabViewModelBase private readonly ISettingsManager settingsManager; private readonly INotificationService notificationService; - public SourceCache ItemsCache { get; } = new(i => i.RepositoryPath + i.ModelName); + public SourceCache ItemsCache { get; } = + new(i => i.RepositoryPath + i.ModelName); public IObservableCollection Categories { get; set; } = new ObservableCollectionExtended(); @@ -76,7 +77,13 @@ public partial class HuggingFacePageViewModel : TabViewModelBase .Connect() .DeferUntilLoaded() .Group(i => i.ModelCategory) - .Transform(g => new CategoryViewModel(g.Cache.Items) { Title = g.Key.GetDescription() ?? g.Key.ToString() }) + .Transform( + g => + new CategoryViewModel(g.Cache.Items) + { + Title = g.Key.GetDescription() ?? g.Key.ToString() + } + ) .SortBy(vm => vm.Title) .Bind(Categories) .WhenAnyPropertyChanged() @@ -137,12 +144,13 @@ public partial class HuggingFacePageViewModel : TabViewModelBase { foreach (var file in viewModel.Item.Files) { - var url = $"https://huggingface.co/{viewModel.Item.RepositoryPath}/resolve/main/{file}?download=true"; + var url = + $"https://huggingface.co/{viewModel.Item.RepositoryPath}/resolve/main/{file}?download=true"; var sharedFolderType = viewModel.Item.ModelCategory.ConvertTo(); var downloadPath = new FilePath( Path.Combine( settingsManager.ModelsDirectory, - sharedFolderType.GetDescription() ?? sharedFolderType.ToString(), + sharedFolderType.ToString(), viewModel.Item.Subfolder ?? string.Empty, file ) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs index 26bf7897..5f8c6c51 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs @@ -49,7 +49,8 @@ public class ControlNetModule : ModuleBase new ComfyNodeBuilder.ControlNetLoader { Name = e.Nodes.GetUniqueName("ControlNetLoader"), - ControlNetName = card.SelectedModel?.FileName ?? throw new ValidationException("No SelectedModel"), + ControlNetName = + card.SelectedModel?.RelativePath ?? throw new ValidationException("No SelectedModel"), } ); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs index 92777c21..7eecc919 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs @@ -71,7 +71,9 @@ public partial class SelectImageCardViewModel(INotificationService notificationS { e.Builder.SetupImagePrimarySource( ImageSource ?? throw new ValidationException("Input Image is required"), - !CurrentBitmapSize.IsEmpty ? CurrentBitmapSize : throw new ValidationException("CurrentBitmapSize is null"), + !CurrentBitmapSize.IsEmpty + ? CurrentBitmapSize + : throw new ValidationException("CurrentBitmapSize is null"), e.Builder.Connections.BatchIndex ); } @@ -98,7 +100,10 @@ public partial class SelectImageCardViewModel(INotificationService notificationS private async Task SelectImageFromFilePickerAsync() { var files = await App.StorageProvider.OpenFilePickerAsync( - new FilePickerOpenOptions { FileTypeFilter = [FilePickerFileTypes.ImagePng, FilePickerFileTypes.ImageJpg] } + new FilePickerOpenOptions + { + FileTypeFilter = [FilePickerFileTypes.ImagePng, FilePickerFileTypes.ImageJpg] + } ); if (files.FirstOrDefault()?.TryGetLocalPath() is { } path) @@ -112,7 +117,10 @@ public partial class SelectImageCardViewModel(INotificationService notificationS /// public void DragOver(object? sender, DragEventArgs e) { - if (e.Data.GetDataFormats().Contains(DataFormats.Files) || e.Data.GetContext() is not null) + if ( + e.Data.GetDataFormats().Contains(DataFormats.Files) + || e.Data.GetContext() is not null + ) { e.Handled = true; return; @@ -134,7 +142,10 @@ public partial class SelectImageCardViewModel(INotificationService notificationS return; } // 2. OS Files - if (e.Data.GetFiles() is { } files && files.Select(f => f.TryGetLocalPath()).FirstOrDefault() is { } path) + if ( + e.Data.GetFiles() is { } files + && files.Select(f => f.TryGetLocalPath()).FirstOrDefault() is { } path + ) { e.Handled = true; @@ -154,7 +165,7 @@ public partial class SelectImageCardViewModel(INotificationService notificationS catch (Exception e) { Logger.Warn(e, "Error loading image"); - notificationService.ShowPersistent("Error loading image", e.Message); + notificationService.Show("Error loading image", e.Message); } } diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs index c1263426..8ef4c918 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs @@ -132,15 +132,16 @@ public partial class MainWindow : AppWindowBase return; // Navigate to first page - Dispatcher - .UIThread - .Post( - () => - navigationService.NavigateTo( - vm.Pages[0], - new BetterSlideNavigationTransition { Effect = SlideNavigationTransitionEffect.FromBottom } - ) - ); + Dispatcher.UIThread.Post( + () => + navigationService.NavigateTo( + vm.Pages[0], + new BetterSlideNavigationTransition + { + Effect = SlideNavigationTransitionEffect.FromBottom + } + ) + ); // Check show update teaching tip if (vm.UpdateViewModel.IsUpdateAvailable) @@ -165,27 +166,24 @@ public partial class MainWindow : AppWindowBase var mainViewModel = (MainWindowViewModel)DataContext!; mainViewModel.SelectedCategory = mainViewModel - .Pages - .Concat(mainViewModel.FooterPages) + .Pages.Concat(mainViewModel.FooterPages) .FirstOrDefault(x => x.GetType() == e.ViewModelType); } private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo) { - Dispatcher - .UIThread - .Post(() => + Dispatcher.UIThread.Post(() => + { + if (DataContext is MainWindowViewModel vm && vm.ShouldShowUpdateAvailableTeachingTip(updateInfo)) { - if (DataContext is MainWindowViewModel vm && vm.ShouldShowUpdateAvailableTeachingTip(updateInfo)) - { - var target = this.FindControl("FooterUpdateItem")!; - var tip = this.FindControl("UpdateAvailableTeachingTip")!; + var target = this.FindControl("FooterUpdateItem")!; + var tip = this.FindControl("UpdateAvailableTeachingTip")!; - tip.Target = target; - tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}"; - tip.IsOpen = true; - } - }); + tip.Target = target; + tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}"; + tip.IsOpen = true; + } + }); } public void SetDefaultFonts() @@ -282,18 +280,16 @@ public partial class MainWindow : AppWindowBase private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e) { - Dispatcher - .UIThread - .Post(() => - { - var fileName = Path.GetFileName(e.Url); - var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName; - notificationService.ShowPersistent( - "Failed to load image", - $"Could not load '{displayName}'\n({e.Exception.Message})", - NotificationType.Warning - ); - }); + Dispatcher.UIThread.Post(() => + { + var fileName = Path.GetFileName(e.Url); + var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName; + notificationService.Show( + "Failed to load image", + $"Could not load '{displayName}'\n({e.Exception.Message})", + NotificationType.Warning + ); + }); } private void TryEnableMicaEffect() @@ -319,7 +315,11 @@ public partial class MainWindow : AppWindowBase else if (ActualThemeVariant == ThemeVariant.Light) { // Similar effect here - var color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Light, out var value) + var color = this.TryFindResource( + "SolidBackgroundFillColorBase", + ThemeVariant.Light, + out var value + ) ? (Color2)(Color)value! : new Color2(243, 243, 243); diff --git a/StabilityMatrix.Core/Models/HybridModelFile.cs b/StabilityMatrix.Core/Models/HybridModelFile.cs index 441b3901..28a5c93a 100644 --- a/StabilityMatrix.Core/Models/HybridModelFile.cs +++ b/StabilityMatrix.Core/Models/HybridModelFile.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using StabilityMatrix.Core.Models.Database; +using StabilityMatrix.Core.Models.FileInterfaces; namespace StabilityMatrix.Core.Models; @@ -67,7 +68,27 @@ public record HybridModelFile return "Default"; } - return Path.GetFileNameWithoutExtension(RelativePath); + var fileName = Path.GetFileNameWithoutExtension(RelativePath); + + if ( + !fileName.Equals("diffusion_pytorch_model", StringComparison.OrdinalIgnoreCase) + && !fileName.Equals("ip_adapter", StringComparison.OrdinalIgnoreCase) + ) + { + return Path.GetFileNameWithoutExtension(RelativePath); + } + + // show a friendlier name when models have the same name like ip_adapter or diffusion_pytorch_model + var directoryName = Path.GetDirectoryName(RelativePath); + if (directoryName is null) + return Path.GetFileNameWithoutExtension(RelativePath); + + var lastIndex = directoryName.LastIndexOf(Path.DirectorySeparatorChar); + if (lastIndex < 0) + return $"{fileName} ({directoryName})"; + + var parentDirectoryName = directoryName.Substring(lastIndex + 1); + return $"{fileName} ({parentDirectoryName})"; } } @@ -83,11 +104,7 @@ public record HybridModelFile public static HybridModelFile FromDownloadable(RemoteResource resource) { - return new HybridModelFile - { - DownloadableResource = resource, - Type = HybridModelType.Downloadable - }; + return new HybridModelFile { DownloadableResource = resource, Type = HybridModelType.Downloadable }; } public string GetId() diff --git a/StabilityMatrix.Core/Python/PipPackageInfo.cs b/StabilityMatrix.Core/Python/PipPackageInfo.cs index 01c588ba..b1d9743b 100644 --- a/StabilityMatrix.Core/Python/PipPackageInfo.cs +++ b/StabilityMatrix.Core/Python/PipPackageInfo.cs @@ -10,4 +10,5 @@ public readonly record struct PipPackageInfo( [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)] [JsonSerializable(typeof(PipPackageInfo))] +[JsonSerializable(typeof(List))] internal partial class PipPackageInfoSerializerContext : JsonSerializerContext;