diff --git a/CHANGELOG.md b/CHANGELOG.md
index 820add42..5791f709 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,15 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
+## 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 8e919e11..3d2bf552 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
@@ -23,29 +23,29 @@
-
-
-
-
+
+
+
+
-
+
-
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
@@ -59,16 +59,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 4f4e7191..9a6ad222 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
@@ -42,9 +42,8 @@ public class ControlNetModule : ModuleBase
{
Name = e.Nodes.GetUniqueName("ControlNet_LoadImage"),
Image =
- card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached(
- "Inference"
- ) ?? throw new ValidationException("No ImageSource")
+ card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached("Inference")
+ ?? throw new ValidationException("No ImageSource")
}
);
@@ -53,8 +52,7 @@ public class ControlNetModule : ModuleBase
{
Name = e.Nodes.GetUniqueName("ControlNetLoader"),
ControlNetName =
- card.SelectedModel?.FileName
- ?? throw new ValidationException("No SelectedModel"),
+ card.SelectedModel?.RelativePath ?? throw new ValidationException("No SelectedModel"),
}
);
@@ -64,10 +62,8 @@ public class ControlNetModule : ModuleBase
Name = e.Nodes.GetUniqueName("ControlNetApply"),
Image = imageLoad.Output1,
ControlNet = controlNetLoader.Output,
- Positive =
- e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"),
- Negative =
- e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"),
+ Positive = e.Temp.Conditioning?.Positive ?? throw new ArgumentException("No Conditioning"),
+ Negative = e.Temp.Conditioning?.Negative ?? throw new ArgumentException("No Conditioning"),
Strength = card.Strength,
StartPercent = card.StartPercent,
EndPercent = card.EndPercent,
@@ -93,10 +89,7 @@ public class ControlNetModule : ModuleBase
}
);
- e.Temp.RefinerConditioning = (
- controlNetRefinerApply.Output1,
- controlNetRefinerApply.Output2
- );
+ e.Temp.RefinerConditioning = (controlNetRefinerApply.Output1, controlNetRefinerApply.Output2);
}
}
}
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;