From 4c816a7353220e387a5fb200c3e4fefd38f38ed9 Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 13 Nov 2024 19:04:58 -0500 Subject: [PATCH 001/297] Add OpenUriCommand --- StabilityMatrix.Avalonia/Helpers/IOCommands.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs index 0c0fd05e..6877ec14 100644 --- a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs +++ b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs @@ -1,4 +1,5 @@ -using CommunityToolkit.Mvvm.Input; +using System; +using CommunityToolkit.Mvvm.Input; using StabilityMatrix.Core.Processes; namespace StabilityMatrix.Avalonia.Helpers; @@ -17,6 +18,18 @@ public static class IOCommands url => !string.IsNullOrWhiteSpace(url) ); + public static RelayCommand OpenUriCommand { get; } = + new( + url => + { + if (url is null) + return; + + ProcessRunner.OpenUrl(url); + }, + url => url is not null + ); + public static AsyncRelayCommand OpenFileBrowserCommand { get; } = new( async path => From 681dd80ec7ebdae56fbf025a9e8bf6259cdc8e0a Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 13 Nov 2024 19:05:35 -0500 Subject: [PATCH 002/297] update mvvm --- StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj index d629b0b6..54b6819c 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj @@ -96,7 +96,7 @@ - + From fa9b3eaf7261b6aa2e738cf9711ca871c0161678 Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 13 Nov 2024 19:05:44 -0500 Subject: [PATCH 003/297] add apizr --- StabilityMatrix.Core/StabilityMatrix.Core.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj index 62d0491c..0ff08e1d 100644 --- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj +++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj @@ -20,6 +20,11 @@ + + + + + From 41a40ec4f9cfbc8412b318aa0d135980a13e0547 Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 13 Nov 2024 19:10:46 -0500 Subject: [PATCH 004/297] Add api client and models --- StabilityMatrix.Core/Api/IOpenModelDbApi.cs | 27 ++++++ .../OpenModelsDb/OpenModelDbArchitecture.cs | 8 ++ .../OpenModelDbArchitecturesResponse.cs | 3 + .../Api/OpenModelsDb/OpenModelDbImage.cs | 88 +++++++++++++++++++ .../Api/OpenModelsDb/OpenModelDbKeyedModel.cs | 23 +++++ .../Api/OpenModelsDb/OpenModelDbModel.cs | 25 ++++++ .../OpenModelsDb/OpenModelDbModelsResponse.cs | 9 ++ .../Api/OpenModelsDb/OpenModelDbResource.cs | 10 +++ .../Models/Api/OpenModelsDb/OpenModelDbTag.cs | 8 ++ .../OpenModelsDb/OpenModelDbTagsResponse.cs | 3 + 10 files changed, 204 insertions(+) create mode 100644 StabilityMatrix.Core/Api/IOpenModelDbApi.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecture.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecturesResponse.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbImage.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModelsResponse.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbResource.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTag.cs create mode 100644 StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTagsResponse.cs diff --git a/StabilityMatrix.Core/Api/IOpenModelDbApi.cs b/StabilityMatrix.Core/Api/IOpenModelDbApi.cs new file mode 100644 index 00000000..451b4088 --- /dev/null +++ b/StabilityMatrix.Core/Api/IOpenModelDbApi.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; +using Apizr.Caching; +using Apizr.Caching.Attributes; +using Apizr.Configuring; +using Refit; +using StabilityMatrix.Core.Models.Api.OpenModelsDb; + +namespace StabilityMatrix.Core.Api; + +[BaseAddress("https://openmodeldb.info")] +public interface IOpenModelDbApi +{ + [Get("/api/v1/models.json"), Cache(CacheMode.GetOrFetch, "0.00:02:00")] + Task GetModels(); + + [Get("/api/v1/tags.json"), Cache(CacheMode.GetOrFetch, "0.00:10:00")] + Task GetTags(); + + [Get("/api/v1/architectures.json"), Cache(CacheMode.GetOrFetch, "0.00:10:00")] + Task GetArchitectures(); +} + +[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] +[JsonSerializable(typeof(OpenModelDbModelsResponse))] +[JsonSerializable(typeof(OpenModelDbTagsResponse))] +[JsonSerializable(typeof(OpenModelDbArchitecturesResponse))] +public partial class OpenModelDbApiJsonContext : JsonSerializerContext; diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecture.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecture.cs new file mode 100644 index 00000000..f9940d5d --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecture.cs @@ -0,0 +1,8 @@ +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbArchitecture +{ + public string? Name { get; set; } + public string? Input { get; set; } + public string[]? CompatiblePlatforms { get; set; } +} diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecturesResponse.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecturesResponse.cs new file mode 100644 index 00000000..a276383d --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecturesResponse.cs @@ -0,0 +1,3 @@ +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbArchitecturesResponse : Dictionary; diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbImage.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbImage.cs new file mode 100644 index 00000000..8074fed1 --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbImage.cs @@ -0,0 +1,88 @@ +using System.Text.Json.Serialization; + +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +/// +/// Apparently all the urls can be either standalone or paired. +/// +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type", IgnoreUnrecognizedTypeDiscriminators = true)] +[JsonDerivedType(typeof(Paired), "paired")] +[JsonDerivedType(typeof(Standalone), "standalone")] +public class OpenModelDbImage +{ + /*{ + "type": "paired", + "LR": "https://images2.imgbox.com/09/3f/ZcIq3bwn_o.jpeg", + "SR": "https://images2.imgbox.com/c7/dd/lIHpU4PZ_o.png", + "thumbnail": "/thumbs/small/6e36848722bccb84eca5232a.jpg" + }*/ + /*{ + "type": "paired", + "LR": "/thumbs/573c7a3162c9831716c3bb35.jpg", + "SR": "/thumbs/9a7cb6631356006c30f177d1.jpg", + "LRSize": { + "width": 366, + "height": 296 + }, + "SRSize": { + "width": 366, + "height": 296 + } + }*/ + public class Paired : OpenModelDbImage + { + [JsonPropertyName("LR")] + public Uri? Lr { get; set; } + + [JsonPropertyName("SR")] + public Uri? Sr { get; set; } + + public Uri? Thumbnail { get; set; } + + public override Uri? GetThumbnailAbsoluteUri() + { + return ToAbsoluteUri(Sr) ?? ToAbsoluteUri(Lr) ?? ToAbsoluteUri(Thumbnail); + } + } + + /* + { + "type": "standalone", + "url": "https://i.slow.pics/rE3PKKTD.webp", + "thumbnail": "/thumbs/small/85e62ea0e6801e7a0bf5acb6.jpg" + } + */ + public class Standalone : OpenModelDbImage + { + public Uri? Url { get; set; } + + public Uri? Thumbnail { get; set; } + + public override Uri? GetThumbnailAbsoluteUri() + { + return ToAbsoluteUri(Url) ?? ToAbsoluteUri(Thumbnail); + } + } + + public static Uri? ToAbsoluteUri(Uri? url) + { + if (url is null) + { + return null; + } + + if (url.IsAbsoluteUri) + { + return url; + } + + var baseUri = new Uri("https://openmodeldb.info/"); + + return new Uri(baseUri, url); + } + + public virtual Uri? GetThumbnailAbsoluteUri() + { + return null; + } +} diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs new file mode 100644 index 00000000..dd280c10 --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs @@ -0,0 +1,23 @@ +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbKeyedModel : OpenModelDbModel +{ + public required string Id { get; set; } + + public OpenModelDbKeyedModel(OpenModelDbModel model) + { + Name = model.Name; + Author = model.Author; + License = model.License; + Tags = model.Tags; + Description = model.Description; + Date = model.Date; + Architecture = model.Architecture; + Size = model.Size; + Scale = model.Scale; + InputChannels = model.InputChannels; + OutputChannels = model.OutputChannels; + Resources = model.Resources; + Images = model.Images; + } +} diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs new file mode 100644 index 00000000..b8c8c3a2 --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using OneOf; +using StabilityMatrix.Core.Converters.Json; + +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbModel +{ + public string? Name { get; set; } + + [JsonConverter(typeof(OneOfJsonConverter))] + public OneOf? Author { get; set; } + public string? License { get; set; } + public List? Tags { get; set; } + public string? Description { get; set; } + public DateOnly? Date { get; set; } + public string? Architecture { get; set; } + public List? Size { get; set; } + public int? Scale { get; set; } + public int? InputChannels { get; set; } + public int? OutputChannels { get; set; } + public List? Resources { get; set; } + public List? Images { get; set; } + public OpenModelDbImage? Thumbnail { get; set; } +} diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModelsResponse.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModelsResponse.cs new file mode 100644 index 00000000..1ee8f769 --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModelsResponse.cs @@ -0,0 +1,9 @@ +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbModelsResponse : Dictionary +{ + public ParallelQuery GetKeyedModels() + { + return this.AsParallel().Select(kv => new OpenModelDbKeyedModel(kv.Value) { Id = kv.Key }); + } +} diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbResource.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbResource.cs new file mode 100644 index 00000000..626738ef --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbResource.cs @@ -0,0 +1,10 @@ +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbResource +{ + public string? Platform { get; set; } + public string? Type { get; set; } + public long Size { get; set; } + public string? Sha256 { get; set; } + public List? Urls { get; set; } +} diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTag.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTag.cs new file mode 100644 index 00000000..3b5398e5 --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTag.cs @@ -0,0 +1,8 @@ +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbTag +{ + public string? Name { get; set; } + public string? Description { get; set; } + public string[]? Implies { get; set; } +} diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTagsResponse.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTagsResponse.cs new file mode 100644 index 00000000..2cb316c6 --- /dev/null +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTagsResponse.cs @@ -0,0 +1,3 @@ +namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; + +public class OpenModelDbTagsResponse : Dictionary; From 738960e6e050abc1295b8073ca22c52b0e99152b Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 13 Nov 2024 19:10:55 -0500 Subject: [PATCH 005/297] Add client registration --- StabilityMatrix.Avalonia/App.axaml.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index e1f01354..7c153e89 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -11,6 +11,8 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Apizr; +using Apizr.Logging; using AsyncAwaitBestPractices; using Avalonia; using Avalonia.Controls; @@ -744,6 +746,24 @@ internal static IServiceCollection ConfigureServices() }) .AddPolicyHandler(retryPolicy); + // Apizr clients + services.AddApizrManagerFor(options => + { + options + .WithRefitSettings( + new RefitSettings( + new SystemTextJsonContentSerializer(OpenModelDbApiJsonContext.Default.Options) + ) + ) + .ConfigureHttpClientBuilder(c => c.AddPolicyHandler(retryPolicy)) + .WithInMemoryCacheHandler() + .WithLogging( + HttpTracerMode.ExceptionsOnly, + HttpMessageParts.AllButResponseBody, + LogLevel.Warning + ); + }); + // Add Refit client managers services.AddHttpClient("A3Client").AddPolicyHandler(localRetryPolicy); From 1dd3e4aa05e5874502c70959e4d5638b1548617d Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 13 Nov 2024 19:11:55 -0500 Subject: [PATCH 006/297] Add openmodeldb views and vm --- .../OpenModelDbBrowserCardViewModel.cs | 40 ++ .../OpenModelDbBrowserViewModel.Filters.cs | 30 ++ .../OpenModelDbBrowserViewModel.cs | 86 ++++ .../ViewModels/CheckpointBrowserViewModel.cs | 9 +- .../Views/OpenModelDbBrowserPage.axaml | 366 ++++++++++++++++++ .../Views/OpenModelDbBrowserPage.axaml.cs | 40 ++ 6 files changed, 567 insertions(+), 4 deletions(-) create mode 100644 StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs create mode 100644 StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml create mode 100644 StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml.cs diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs new file mode 100644 index 00000000..c654bfe0 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using Nito.Disposables.Internals; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Core.Extensions; +using StabilityMatrix.Core.Models.Api.OpenModelsDb; + +namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; + +public partial class OpenModelDbBrowserCardViewModel : DisposableViewModelBase +{ + private static Uri UsersBaseUri => new("https://openmodeldb.info/users"); + private static Uri ModelsBaseUri => new("https://openmodeldb.info/models"); + + public OpenModelDbKeyedModel? Model { get; set; } + + public Uri? ModelUri => Model is { } model ? ModelsBaseUri.Append(model.Id) : null; + + public Uri? ThumbnailUri => + Model?.Thumbnail?.GetThumbnailAbsoluteUri() + ?? Model?.Images?.Select(image => image.GetThumbnailAbsoluteUri()).WhereNotNull().FirstOrDefault(); + + public string? DefaultAuthor + { + get + { + if (Model?.Author?.Value is string author) + { + return author; + } + if (Model?.Author?.Value is string[] { Length: > 0 } authorArray) + { + return authorArray.First(); + } + return null; + } + } + + public Uri? DefaultAuthorProfileUri => DefaultAuthor is { } author ? UsersBaseUri.Append(author) : null; +} diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs new file mode 100644 index 00000000..6063c2d7 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using DynamicData.Binding; +using StabilityMatrix.Core.Models.Api.OpenModelsDb; + +namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; + +public partial class OpenModelDbBrowserViewModel +{ + private IObservable> SearchQueryPredicate => + this.WhenPropertyChanged(vm => vm.SearchQuery) + .Throttle(TimeSpan.FromMilliseconds(50)) + .Select(pv => CreateSearchQueryPredicate(pv.Value)) + .StartWith(CreateSearchQueryPredicate(null)) + .AsObservable(); + + private static Func CreateSearchQueryPredicate(string? text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return static _ => true; + } + + return x => + x.Name?.Contains(text, StringComparison.OrdinalIgnoreCase) == true + || x.Tags?.Any(tag => tag.Contains(text, StringComparison.OrdinalIgnoreCase)) == true + || x.Description?.Contains(text, StringComparison.OrdinalIgnoreCase) == true; + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs new file mode 100644 index 00000000..7b7c5ae9 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs @@ -0,0 +1,86 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Apizr; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using DynamicData; +using DynamicData.Binding; +using Microsoft.Extensions.Logging; +using StabilityMatrix.Avalonia.Services; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Avalonia.Views; +using StabilityMatrix.Core.Api; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models.Api.OpenModelsDb; + +namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; + +[View(typeof(OpenModelDbBrowserPage))] +[Singleton] +public partial class OpenModelDbBrowserViewModel( + ILogger logger, + IApizrManager apiManager, + INotificationService notificationService +) : TabViewModelBase +{ + // ReSharper disable once LocalizableElement + public override string Header => "OpenModelDB"; + + [ObservableProperty] + private bool isLoading; + + [ObservableProperty] + private string? searchQuery; + + private readonly SourceCache modelCache = new(static x => x.Id); + + public IObservableCollection FilteredModelCards { get; } = + new ObservableCollectionExtended(); + + protected override void OnInitialLoaded() + { + base.OnInitialLoaded(); + + modelCache + .Connect() + .DeferUntilLoaded() + .Filter(SearchQueryPredicate) + .Transform(model => new OpenModelDbBrowserCardViewModel { Model = model }) + .SortAndBind( + FilteredModelCards, + SortExpressionComparer.Descending( + card => card.Model?.Date ?? DateOnly.MinValue + ) + ) + .Subscribe(); + } + + [RelayCommand] + private async Task SearchAsync() + { + try + { + await LoadDataAsync(); + } + catch (ApizrException e) + { + logger.LogWarning(e, "Failed to load models from OpenModelDB"); + notificationService.ShowPersistent("Failed to load models from OpenModelDB", e.Message); + } + } + + /// + /// Populate the model cache from api. + /// + private async Task LoadDataAsync() + { + var response = await apiManager.ExecuteAsync(api => api.GetModels()); + + modelCache.Edit(innerCache => + { + innerCache.Load(response.GetKeyedModels()); + }); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs index 420f3232..cf4a931c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs @@ -31,13 +31,14 @@ public partial class CheckpointBrowserViewModel : PageViewModelBase /// public CheckpointBrowserViewModel( CivitAiBrowserViewModel civitAiBrowserViewModel, - HuggingFacePageViewModel huggingFaceViewModel + HuggingFacePageViewModel huggingFaceViewModel, + OpenModelDbBrowserViewModel openModelDbBrowserViewModel ) { Pages = new List( - new List([civitAiBrowserViewModel, huggingFaceViewModel]).Select( - vm => new TabItem { Header = vm.Header, Content = vm } - ) + new List( + [civitAiBrowserViewModel, huggingFaceViewModel, openModelDbBrowserViewModel] + ).Select(vm => new TabItem { Header = vm.Header, Content = vm }) ); SelectedPage = Pages.FirstOrDefault(); EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested; diff --git a/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml new file mode 100644 index 00000000..826f3f26 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml.cs b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml.cs new file mode 100644 index 00000000..2b5263e0 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml.cs @@ -0,0 +1,40 @@ +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views; + +[Singleton] +public partial class OpenModelDbBrowserPage : UserControlBase +{ + public OpenModelDbBrowserPage() + { + InitializeComponent(); + } + /*private readonly ISettingsManager settingsManager; + + public OpenModelDbBrowserPage(ISettingsManager settingsManager) + { + this.settingsManager = settingsManager; + InitializeComponent(); + } + + private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) + { + if (sender is not ScrollViewer scrollViewer) + return; + + if (scrollViewer.Offset.Y == 0) + return; + + var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f; + + if ( + isAtEnd + && settingsManager.Settings.IsWorkflowInfiniteScrollEnabled + && DataContext is IInfinitelyScroll scroll + ) + { + scroll.LoadNextPageAsync().SafeFireAndForget(); + } + }*/ +} From df9eae52d7ed9be38240d41c8c9ac41daa2a5ea9 Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 13 Nov 2024 19:12:14 -0500 Subject: [PATCH 007/297] Add resource string for OpenOnOpenModelDb --- StabilityMatrix.Avalonia/Languages/Resources.Designer.cs | 9 +++++++++ StabilityMatrix.Avalonia/Languages/Resources.resx | 3 +++ 2 files changed, 12 insertions(+) diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs index 4b1fcef9..81919961 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs +++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs @@ -401,6 +401,15 @@ public static string Action_OpenOnOpenArt { } } + /// + /// Looks up a localized string similar to Open on OpenModelDB. + /// + public static string Action_OpenOnOpenModelDb { + get { + return ResourceManager.GetString("Action_OpenOnOpenModelDb", resourceCulture); + } + } + /// /// Looks up a localized string similar to Open Project.... /// diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx index 12ccc82f..a3586c04 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.resx @@ -1302,4 +1302,7 @@ - or - + + Open on OpenModelDB + From 7ee32718c96a32bfdda327a53327d7e710257445 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sun, 17 Nov 2024 21:35:24 -0500 Subject: [PATCH 008/297] Add tag label styles --- StabilityMatrix.Avalonia/App.axaml | 4 +- .../ControlThemes/LabelStyles.Dark.axaml | 148 ++++++ .../Styles/ControlThemes/LabelStyles.axaml | 421 ++++++++++++++++++ .../Styles/ControlThemes/_index.axaml | 12 + 4 files changed, 582 insertions(+), 3 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.Dark.axaml create mode 100644 StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml create mode 100644 StabilityMatrix.Avalonia/Styles/ControlThemes/_index.axaml diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index 78886b1f..6ea23012 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -23,9 +23,7 @@ - - - + diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.Dark.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.Dark.axaml new file mode 100644 index 00000000..9150bf4f --- /dev/null +++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.Dark.axaml @@ -0,0 +1,148 @@ + + + + 0.3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml new file mode 100644 index 00000000..c05c4ca9 --- /dev/null +++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml @@ -0,0 +1,421 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 8 2 + 8 4 + 20 + 24 + 12 + 3 + 9999 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/_index.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/_index.axaml new file mode 100644 index 00000000..723dceaf --- /dev/null +++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/_index.axaml @@ -0,0 +1,12 @@ + + + + + + + + + From dddd122ab911c13f64eb42fd1a29bba8695e736c Mon Sep 17 00:00:00 2001 From: Ionite Date: Sun, 17 Nov 2024 21:38:08 -0500 Subject: [PATCH 009/297] Use OpenModelDbManager --- StabilityMatrix.Avalonia/App.axaml.cs | 11 ++-- .../Api/OpenModelsDb/OpenModelDbKeyedModel.cs | 19 ++----- .../Api/OpenModelsDb/OpenModelDbModel.cs | 22 +++++++- .../Services/OpenModelDbManager.cs | 52 +++++++++++++++++++ .../StabilityMatrix.Core.csproj | 1 - 5 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 StabilityMatrix.Core/Services/OpenModelDbManager.cs diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 7c153e89..b99d6162 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -747,7 +747,7 @@ internal static IServiceCollection ConfigureServices() .AddPolicyHandler(retryPolicy); // Apizr clients - services.AddApizrManagerFor(options => + services.AddApizrManagerFor(options => { options .WithRefitSettings( @@ -757,12 +757,11 @@ internal static IServiceCollection ConfigureServices() ) .ConfigureHttpClientBuilder(c => c.AddPolicyHandler(retryPolicy)) .WithInMemoryCacheHandler() - .WithLogging( - HttpTracerMode.ExceptionsOnly, - HttpMessageParts.AllButResponseBody, - LogLevel.Warning - ); + .WithLogging(HttpTracerMode.ExceptionsOnly, HttpMessageParts.AllButResponseBody); }); + services.AddSingleton( + sp => (OpenModelDbManager)sp.GetRequiredService>() + ); // Add Refit client managers services.AddHttpClient("A3Client").AddPolicyHandler(localRetryPolicy); diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs index dd280c10..7d3d0467 100644 --- a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs @@ -1,23 +1,12 @@ namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; -public class OpenModelDbKeyedModel : OpenModelDbModel +public record OpenModelDbKeyedModel : OpenModelDbModel { public required string Id { get; set; } - public OpenModelDbKeyedModel(OpenModelDbModel model) + public OpenModelDbKeyedModel(OpenModelDbKeyedModel model) + : base(model) { - Name = model.Name; - Author = model.Author; - License = model.License; - Tags = model.Tags; - Description = model.Description; - Date = model.Date; - Architecture = model.Architecture; - Size = model.Size; - Scale = model.Scale; - InputChannels = model.InputChannels; - OutputChannels = model.OutputChannels; - Resources = model.Resources; - Images = model.Images; + Id = model.Id; } } diff --git a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs index b8c8c3a2..272b7a5f 100644 --- a/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs +++ b/StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs @@ -4,7 +4,7 @@ namespace StabilityMatrix.Core.Models.Api.OpenModelsDb; -public class OpenModelDbModel +public record OpenModelDbModel { public string? Name { get; set; } @@ -22,4 +22,24 @@ public class OpenModelDbModel public List? Resources { get; set; } public List? Images { get; set; } public OpenModelDbImage? Thumbnail { get; set; } + + public OpenModelDbModel() { } + + public OpenModelDbModel(OpenModelDbModel model) + { + Name = model.Name; + Author = model.Author; + License = model.License; + Tags = model.Tags; + Description = model.Description; + Date = model.Date; + Architecture = model.Architecture; + Size = model.Size; + Scale = model.Scale; + InputChannels = model.InputChannels; + OutputChannels = model.OutputChannels; + Resources = model.Resources; + Images = model.Images; + Thumbnail = model.Thumbnail; + } } diff --git a/StabilityMatrix.Core/Services/OpenModelDbManager.cs b/StabilityMatrix.Core/Services/OpenModelDbManager.cs new file mode 100644 index 00000000..1e0b09fd --- /dev/null +++ b/StabilityMatrix.Core/Services/OpenModelDbManager.cs @@ -0,0 +1,52 @@ +using System.Diagnostics.CodeAnalysis; +using Apizr; +using Apizr.Caching; +using Apizr.Caching.Attributes; +using Apizr.Configuring.Manager; +using Apizr.Connecting; +using Apizr.Mapping; +using Fusillade; +using Polly.Registry; +using StabilityMatrix.Core.Api; +using StabilityMatrix.Core.Models.Api.OpenModelsDb; + +namespace StabilityMatrix.Core.Services; + +public class OpenModelDbManager( + ILazyFactory lazyWebApi, + IConnectivityHandler connectivityHandler, + ICacheHandler cacheHandler, + IMappingHandler mappingHandler, + ILazyFactory> lazyResiliencePipelineRegistry, + IApizrManagerOptions apizrOptions +) + : ApizrManager( + lazyWebApi, + connectivityHandler, + cacheHandler, + mappingHandler, + lazyResiliencePipelineRegistry, + apizrOptions + ) +{ + public Uri UsersBaseUri => new("https://openmodeldb.info/users"); + + public Uri ModelsBaseUri => new("https://openmodeldb.info/models"); + + public IReadOnlyDictionary? Tags { get; private set; } + + public IReadOnlyDictionary? Architectures { get; private set; } + + [MemberNotNull(nameof(Tags), nameof(Architectures))] + public async Task EnsureMetadataLoadedAsync(Priority priority = default) + { + if (Tags is null) + { + Tags = await ExecuteAsync(api => api.GetTags()).ConfigureAwait(false); + } + if (Architectures is null) + { + Architectures = await ExecuteAsync(api => api.GetArchitectures()).ConfigureAwait(false); + } + } +} diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj index 0ff08e1d..7237d02e 100644 --- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj +++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj @@ -24,7 +24,6 @@ - From 4705dc448b282a75c8205dae71f46a3f4db92039 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sun, 17 Nov 2024 21:38:23 -0500 Subject: [PATCH 010/297] Add FileSizeConverters --- .../Converters/FileSizeConverters.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs diff --git a/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs b/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs new file mode 100644 index 00000000..27cfae20 --- /dev/null +++ b/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.CodeAnalysis; +using Avalonia.Data.Converters; +using StabilityMatrix.Core.Models; + +namespace StabilityMatrix.Avalonia.Converters; + +[SuppressMessage("ReSharper", "LocalizableElement")] +public static class FileSizeConverters +{ + public static FuncValueConverter HumanizeFileSizeConverter { get; } = + new(value => + { + if (value is not { } size) + { + return string.Empty; + } + + string[] sizeUnits = ["KB", "MB", "GB", "TB"]; + + var unitIndex = 0; + + while (size >= 1000 && unitIndex < sizeUnits.Length - 1) + { + size /= 1000; + unitIndex++; + } + + return $"{size:0.##} {sizeUnits[unitIndex]}"; + }); + + public static FuncValueConverter HumanizeBinaryFileSizeConverter { get; } = + new(value => + { + if (value is not { } size) + { + return string.Empty; + } + + string[] sizeUnits = ["KiB", "MiB", "GiB", "TiB"]; + + var unitIndex = 0; + + while (size >= 1024 && unitIndex < sizeUnits.Length - 1) + { + size /= 1024; + unitIndex++; + } + + return $"{size:0.##} {sizeUnits[unitIndex]}"; + }); +} From 589c6eb8f0462b3f26e9dc119d8d5eefa6a4a259 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sun, 17 Nov 2024 21:39:45 -0500 Subject: [PATCH 011/297] Add tags to openmodeldb page --- .../OpenModelDbBrowserCardViewModel.cs | 26 ++++-- .../OpenModelDbBrowserViewModel.Filters.cs | 19 +++-- .../OpenModelDbBrowserViewModel.cs | 45 +++++++---- .../Views/OpenModelDbBrowserPage.axaml | 79 +++++++++++++------ 4 files changed, 115 insertions(+), 54 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs index c654bfe0..63eb02b5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs @@ -1,25 +1,36 @@ using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; using Nito.Disposables.Internals; -using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Models.Api.OpenModelsDb; +using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; -public partial class OpenModelDbBrowserCardViewModel : DisposableViewModelBase +[Localizable(false)] +public sealed class OpenModelDbBrowserCardViewModel(OpenModelDbManager openModelDbManager) { - private static Uri UsersBaseUri => new("https://openmodeldb.info/users"); - private static Uri ModelsBaseUri => new("https://openmodeldb.info/models"); - public OpenModelDbKeyedModel? Model { get; set; } - public Uri? ModelUri => Model is { } model ? ModelsBaseUri.Append(model.Id) : null; + public Uri? ModelUri => Model is { } model ? openModelDbManager.ModelsBaseUri.Append(model.Id) : null; public Uri? ThumbnailUri => Model?.Thumbnail?.GetThumbnailAbsoluteUri() ?? Model?.Images?.Select(image => image.GetThumbnailAbsoluteUri()).WhereNotNull().FirstOrDefault(); + public IEnumerable Tags => + Model?.Tags?.Select(tagId => openModelDbManager.Tags?.GetValueOrDefault(tagId)).WhereNotNull() ?? []; + + public OpenModelDbArchitecture? Architecture => + Model?.Architecture is { } architectureId + ? openModelDbManager.Architectures?.GetValueOrDefault(architectureId) + : null; + + public string? DisplayScale => Model?.Scale is { } scale ? $"{scale}x" : null; + public string? DefaultAuthor { get @@ -36,5 +47,6 @@ public string? DefaultAuthor } } - public Uri? DefaultAuthorProfileUri => DefaultAuthor is { } author ? UsersBaseUri.Append(author) : null; + public Uri? DefaultAuthorProfileUri => + DefaultAuthor is { } author ? openModelDbManager.UsersBaseUri.Append(author) : null; } diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs index 6063c2d7..0d1984fd 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs @@ -1,18 +1,26 @@ using System; using System.Linq; +using System.Reactive; using System.Reactive.Linq; -using DynamicData.Binding; +using System.Reactive.Subjects; +using System.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using StabilityMatrix.Core.Models.Api.OpenModelsDb; namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; public partial class OpenModelDbBrowserViewModel { + [ObservableProperty] + private string? searchQuery; + + private Subject SearchQueryReload { get; } = new(); + private IObservable> SearchQueryPredicate => - this.WhenPropertyChanged(vm => vm.SearchQuery) - .Throttle(TimeSpan.FromMilliseconds(50)) - .Select(pv => CreateSearchQueryPredicate(pv.Value)) + SearchQueryReload + .Select(pv => CreateSearchQueryPredicate(SearchQuery)) .StartWith(CreateSearchQueryPredicate(null)) + .ObserveOn(SynchronizationContext.Current!) .AsObservable(); private static Func CreateSearchQueryPredicate(string? text) @@ -24,7 +32,6 @@ private static Func CreateSearchQueryPredicate(string? t return x => x.Name?.Contains(text, StringComparison.OrdinalIgnoreCase) == true - || x.Tags?.Any(tag => tag.Contains(text, StringComparison.OrdinalIgnoreCase)) == true - || x.Description?.Contains(text, StringComparison.OrdinalIgnoreCase) == true; + || x.Tags?.Any(tag => tag.StartsWith(text, StringComparison.OrdinalIgnoreCase)) == true; } } diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs index 7b7c5ae9..15af9557 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs @@ -1,27 +1,30 @@ using System; -using System.Linq; +using System.Reactive; using System.Reactive.Linq; +using System.Threading; using System.Threading.Tasks; using Apizr; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using DynamicData; using DynamicData.Binding; +using Fusillade; using Microsoft.Extensions.Logging; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.Views; -using StabilityMatrix.Core.Api; using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Models.Api.OpenModelsDb; +using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; [View(typeof(OpenModelDbBrowserPage))] [Singleton] -public partial class OpenModelDbBrowserViewModel( +public sealed partial class OpenModelDbBrowserViewModel( ILogger logger, - IApizrManager apiManager, + OpenModelDbManager openModelDbManager, INotificationService notificationService ) : TabViewModelBase { @@ -31,10 +34,7 @@ INotificationService notificationService [ObservableProperty] private bool isLoading; - [ObservableProperty] - private string? searchQuery; - - private readonly SourceCache modelCache = new(static x => x.Id); + public SourceCache ModelCache { get; } = new(static x => x.Id); public IObservableCollection FilteredModelCards { get; } = new ObservableCollectionExtended(); @@ -43,17 +43,18 @@ protected override void OnInitialLoaded() { base.OnInitialLoaded(); - modelCache + ModelCache .Connect() .DeferUntilLoaded() .Filter(SearchQueryPredicate) - .Transform(model => new OpenModelDbBrowserCardViewModel { Model = model }) + .Transform(model => new OpenModelDbBrowserCardViewModel(openModelDbManager) { Model = model }) .SortAndBind( FilteredModelCards, SortExpressionComparer.Descending( card => card.Model?.Date ?? DateOnly.MinValue ) ) + .ObserveOn(SynchronizationContext.Current!) .Subscribe(); } @@ -69,18 +70,32 @@ private async Task SearchAsync() logger.LogWarning(e, "Failed to load models from OpenModelDB"); notificationService.ShowPersistent("Failed to load models from OpenModelDB", e.Message); } + + SearchQueryReload.OnNext(Unit.Default); } /// /// Populate the model cache from api. /// - private async Task LoadDataAsync() + private async Task LoadDataAsync(Priority priority = default) { - var response = await apiManager.ExecuteAsync(api => api.GetModels()); + await openModelDbManager.EnsureMetadataLoadedAsync(); + + var response = await openModelDbManager.ExecuteAsync( + api => api.GetModels(), + options => options.WithPriority(priority) + ); - modelCache.Edit(innerCache => + if (ModelCache.Count == 0) { - innerCache.Load(response.GetKeyedModels()); - }); + ModelCache.Edit(innerCache => + { + innerCache.Load(response.GetKeyedModels()); + }); + } + else + { + ModelCache.EditDiff(response.GetKeyedModels()); + } } } diff --git a/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml index 826f3f26..fcda6df9 100644 --- a/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml @@ -7,6 +7,7 @@ xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia" xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls" @@ -17,8 +18,9 @@ xmlns:vendorLabs="clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs" xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" xmlns:vmCheckpointBrowser="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser" - d:DesignHeight="450" - d:DesignWidth="800" + d:DataContext="{x:Static designData:DesignData.OpenModelDbBrowserViewModel}" + d:DesignHeight="650" + d:DesignWidth="900" x:DataType="vmCheckpointBrowser:OpenModelDbBrowserViewModel" mc:Ignorable="d"> @@ -68,6 +70,17 @@ + + + - + TextAlignment="Center" /> + --> + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Styles/SemiStyles.axaml.cs b/StabilityMatrix.Avalonia/Styles/SemiStyles.axaml.cs new file mode 100644 index 00000000..b68b045f --- /dev/null +++ b/StabilityMatrix.Avalonia/Styles/SemiStyles.axaml.cs @@ -0,0 +1,12 @@ +using System; +using Avalonia.Markup.Xaml; + +namespace StabilityMatrix.Avalonia.Styles; + +public partial class SemiStyles : global::Avalonia.Styling.Styles +{ + public SemiStyles(IServiceProvider? provider = null) + { + AvaloniaXamlLoader.Load(provider, this); + } +} From 136ce31aeec5135438a9c2a447bd66f3ccba80c8 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 18 Nov 2024 17:57:58 -0500 Subject: [PATCH 016/297] Fix FileSizeConverter not working, change to object type --- .../Converters/FileSizeConverters.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs b/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs index 27cfae20..1d0622e2 100644 --- a/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs +++ b/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Data.Converters; using StabilityMatrix.Core.Models; @@ -7,13 +8,10 @@ namespace StabilityMatrix.Avalonia.Converters; [SuppressMessage("ReSharper", "LocalizableElement")] public static class FileSizeConverters { - public static FuncValueConverter HumanizeFileSizeConverter { get; } = + public static FuncValueConverter HumanizeFileSizeConverter { get; } = new(value => { - if (value is not { } size) - { - return string.Empty; - } + var size = Convert.ToDouble(value); string[] sizeUnits = ["KB", "MB", "GB", "TB"]; @@ -28,13 +26,10 @@ public static class FileSizeConverters return $"{size:0.##} {sizeUnits[unitIndex]}"; }); - public static FuncValueConverter HumanizeBinaryFileSizeConverter { get; } = + public static FuncValueConverter HumanizeBinaryFileSizeConverter { get; } = new(value => { - if (value is not { } size) - { - return string.Empty; - } + var size = Convert.ToDouble(value); string[] sizeUnits = ["KiB", "MiB", "GiB", "TiB"]; From b3184bf9cdca3f83430272e747fef5771fae782b Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 18 Nov 2024 17:58:20 -0500 Subject: [PATCH 017/297] Refactor for image changes --- .../CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs index 63eb02b5..80a5e91c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs @@ -18,8 +18,12 @@ public sealed class OpenModelDbBrowserCardViewModel(OpenModelDbManager openModel public Uri? ModelUri => Model is { } model ? openModelDbManager.ModelsBaseUri.Append(model.Id) : null; public Uri? ThumbnailUri => - Model?.Thumbnail?.GetThumbnailAbsoluteUri() - ?? Model?.Images?.Select(image => image.GetThumbnailAbsoluteUri()).WhereNotNull().FirstOrDefault(); + Model?.Thumbnail?.GetImageAbsoluteUris().FirstOrDefault() + ?? Model + ?.Images + ?.Select(image => image.GetImageAbsoluteUris().FirstOrDefault()) + .WhereNotNull() + .FirstOrDefault(); public IEnumerable Tags => Model?.Tags?.Select(tagId => openModelDbManager.Tags?.GetValueOrDefault(tagId)).WhereNotNull() ?? []; From 21c877aaf0892ad9d4f16588ffd80932f6d83a45 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 18 Nov 2024 17:59:47 -0500 Subject: [PATCH 018/297] Add openmodeldb ModelDetails vm --- .../OpenModelDbBrowserViewModel.cs | 17 ++ .../OpenModelDbModelDetailsViewModel.cs | 64 ++++++ .../OpenModelDbModelDetailsDialog.axaml | 208 ++++++++++++++++++ .../OpenModelDbModelDetailsDialog.axaml.cs | 13 ++ .../Views/OpenModelDbBrowserPage.axaml | 2 +- 5 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs create mode 100644 StabilityMatrix.Avalonia/Views/Dialogs/OpenModelDbModelDetailsDialog.axaml create mode 100644 StabilityMatrix.Avalonia/Views/Dialogs/OpenModelDbModelDetailsDialog.axaml.cs diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs index 15af9557..5001ed8f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs @@ -12,7 +12,9 @@ using Microsoft.Extensions.Logging; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Avalonia.Views; +using StabilityMatrix.Avalonia.Views.Dialogs; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Models.Api.OpenModelsDb; @@ -24,6 +26,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; [Singleton] public sealed partial class OpenModelDbBrowserViewModel( ILogger logger, + ServiceManager vmManager, OpenModelDbManager openModelDbManager, INotificationService notificationService ) : TabViewModelBase @@ -74,6 +77,20 @@ private async Task SearchAsync() SearchQueryReload.OnNext(Unit.Default); } + [RelayCommand] + private async Task OpenModelCardAsync(OpenModelDbBrowserCardViewModel? card) + { + if (card?.Model is not { } model) + { + return; + } + + var vm = vmManager.Get(); + vm.Model = model; + + await vm.GetDialog().ShowAsync(); + } + /// /// Populate the model cache from api. /// diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs new file mode 100644 index 00000000..42a72598 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Avalonia; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Avalonia.Views.Dialogs; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models.Api.OpenModelsDb; +using StabilityMatrix.Core.Services; + +namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; + +[View(typeof(OpenModelDbModelDetailsDialog))] +[ManagedService] +[Transient] +public partial class OpenModelDbModelDetailsViewModel( + OpenModelDbManager openModelDbManager, + IModelIndexService modelIndexService +) : ContentDialogViewModelBase +{ + public class ModelResourceViewModel(IModelIndexService modelIndexService) + { + public required OpenModelDbResource Resource { get; init; } + + public string DisplayName => $"{Resource.Platform} (.{Resource.Type} file)"; + + // todo: idk + public bool IsInstalled => false; + } + + [Required] + public OpenModelDbKeyedModel? Model { get; set; } + + public IEnumerable ImageUris => Model?.Images?.SelectImageAbsoluteUris() ?? []; + + public IEnumerable Resources => + Model + ?.Resources + ?.Select(resource => new ModelResourceViewModel(modelIndexService) { Resource = resource }) ?? []; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CanImport))] + private ModelResourceViewModel? selectedResource; + + public bool CanImport => SelectedResource is not null; + + [RelayCommand] + private async Task ImportAsync(ModelResourceViewModel resourceVm) { } + + public override BetterContentDialog GetDialog() + { + var dialog = base.GetDialog(); + dialog.IsFooterVisible = false; + dialog.CloseOnClickOutside = true; + dialog.FullSizeDesired = true; + dialog.ContentMargin = new Thickness(8); + return dialog; + } +} diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OpenModelDbModelDetailsDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/OpenModelDbModelDetailsDialog.axaml new file mode 100644 index 00000000..fe6ba967 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/OpenModelDbModelDetailsDialog.axaml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 90d013124e0ae2a3b92197b7a12646babba6932a Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 7 Dec 2024 20:16:41 -0800 Subject: [PATCH 069/297] placeholder images & sort stuffs & throw everything in esrgan --- .../OpenModelDbBrowserCardViewModel.cs | 3 +- .../OpenModelDbBrowserViewModel.Filters.cs | 34 +++++++++++++++++++ .../OpenModelDbBrowserViewModel.cs | 14 ++++---- .../OpenModelDbModelDetailsViewModel.cs | 5 ++- .../Views/OpenModelDbBrowserPage.axaml | 6 ++-- .../Api/OpenModelsDb/OpenModelDbKeyedModel.cs | 4 +-- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs index 80a5e91c..ff2043bb 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs @@ -23,7 +23,8 @@ public sealed class OpenModelDbBrowserCardViewModel(OpenModelDbManager openModel ?.Images ?.Select(image => image.GetImageAbsoluteUris().FirstOrDefault()) .WhereNotNull() - .FirstOrDefault(); + .FirstOrDefault() + ?? Assets.NoImage; public IEnumerable Tags => Model?.Tags?.Select(tagId => openModelDbManager.Tags?.GetValueOrDefault(tagId)).WhereNotNull() ?? []; diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs index 0d1984fd..817d518b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs @@ -1,10 +1,12 @@ using System; +using System.ComponentModel; using System.Linq; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using DynamicData.Binding; using StabilityMatrix.Core.Models.Api.OpenModelsDb; namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; @@ -23,6 +25,16 @@ public partial class OpenModelDbBrowserViewModel .ObserveOn(SynchronizationContext.Current!) .AsObservable(); + private IObservable> SortComparer => + Observable + .FromEventPattern(this, nameof(PropertyChanged)) + .Where(x => x.EventArgs.PropertyName is nameof(SelectedSortOption)) + .Throttle(TimeSpan.FromMilliseconds(50)) + .Select(_ => GetSortComparer(SelectedSortOption)) + .StartWith(GetSortComparer(SelectedSortOption)) + .ObserveOn(SynchronizationContext.Current!) + .AsObservable(); + private static Func CreateSearchQueryPredicate(string? text) { if (string.IsNullOrWhiteSpace(text)) @@ -34,4 +46,26 @@ private static Func CreateSearchQueryPredicate(string? t x.Name?.Contains(text, StringComparison.OrdinalIgnoreCase) == true || x.Tags?.Any(tag => tag.StartsWith(text, StringComparison.OrdinalIgnoreCase)) == true; } + + private static SortExpressionComparer GetSortComparer( + string sortOption + ) => + sortOption switch + { + "Latest" + => SortExpressionComparer.Descending(x => x.Model?.Date), + "Largest Scale" + => SortExpressionComparer.Descending(x => x.Model?.Scale), + "Smallest Scale" + => SortExpressionComparer.Ascending(x => x.Model?.Scale), + "Largest Size" + => SortExpressionComparer.Descending( + x => x.Model?.Size?.FirstOrDefault() + ), + "Smallest Size" + => SortExpressionComparer.Ascending( + x => x.Model?.Size?.FirstOrDefault() + ), + _ => SortExpressionComparer.Descending(x => x.Model?.Date) + }; } diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs index 5001ed8f..dc10be31 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reactive; using System.Reactive.Linq; using System.Threading; @@ -37,11 +38,17 @@ INotificationService notificationService [ObservableProperty] private bool isLoading; + [ObservableProperty] + private string selectedSortOption = "Latest"; + public SourceCache ModelCache { get; } = new(static x => x.Id); public IObservableCollection FilteredModelCards { get; } = new ObservableCollectionExtended(); + public List SortOptions => + ["Latest", "Largest Scale", "Smallest Scale", "Largest Size", "Smallest Size"]; + protected override void OnInitialLoaded() { base.OnInitialLoaded(); @@ -51,12 +58,7 @@ protected override void OnInitialLoaded() .DeferUntilLoaded() .Filter(SearchQueryPredicate) .Transform(model => new OpenModelDbBrowserCardViewModel(openModelDbManager) { Model = model }) - .SortAndBind( - FilteredModelCards, - SortExpressionComparer.Descending( - card => card.Model?.Date ?? DateOnly.MinValue - ) - ) + .SortAndBind(FilteredModelCards, SortComparer) .ObserveOn(SynchronizationContext.Current!) .Subscribe(); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs index 86419d0f..de016251 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs @@ -44,7 +44,10 @@ public class ModelResourceViewModel(IModelIndexService modelIndexService) [Required] public OpenModelDbKeyedModel? Model { get; set; } - public IEnumerable ImageUris => Model?.Images?.SelectImageAbsoluteUris() ?? []; + public IEnumerable ImageUris => + Model?.Images?.SelectImageAbsoluteUris().Any() ?? false + ? Model?.Images?.SelectImageAbsoluteUris() ?? [Assets.NoImage] + : [Assets.NoImage]; public IEnumerable Resources => Model diff --git a/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml index 700a1eeb..300fd612 100644 --- a/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml @@ -323,10 +323,10 @@ IsVisible="{Binding SearchQuery, Converter={x:Static StringConverters.IsNullOrEmpty}}" Orientation="Vertical"> SharedFolderType.ESRGAN, - _ => null + "swinir" => SharedFolderType.SwinIR, + _ => SharedFolderType.ESRGAN }; } } From fb412a6983f81805712426802513fc5f4ea078c8 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sun, 8 Dec 2024 14:03:27 -0500 Subject: [PATCH 070/297] Add MeansImplicitUse to di attributes --- StabilityMatrix.Core/Attributes/ManagedServiceAttribute.cs | 3 ++- StabilityMatrix.Core/Attributes/SingletonAttribute.cs | 2 ++ StabilityMatrix.Core/Attributes/TransientAttribute.cs | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Core/Attributes/ManagedServiceAttribute.cs b/StabilityMatrix.Core/Attributes/ManagedServiceAttribute.cs index 41f0fd1f..c963b96b 100644 --- a/StabilityMatrix.Core/Attributes/ManagedServiceAttribute.cs +++ b/StabilityMatrix.Core/Attributes/ManagedServiceAttribute.cs @@ -3,6 +3,7 @@ namespace StabilityMatrix.Core.Attributes; -[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors), MeansImplicitUse] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +[MeansImplicitUse(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.Itself)] [AttributeUsage(AttributeTargets.Class)] public class ManagedServiceAttribute : Attribute; diff --git a/StabilityMatrix.Core/Attributes/SingletonAttribute.cs b/StabilityMatrix.Core/Attributes/SingletonAttribute.cs index 39e1c8cd..62aa0c5d 100644 --- a/StabilityMatrix.Core/Attributes/SingletonAttribute.cs +++ b/StabilityMatrix.Core/Attributes/SingletonAttribute.cs @@ -1,8 +1,10 @@ using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; namespace StabilityMatrix.Core.Attributes; [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +[MeansImplicitUse(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.Itself)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] public class SingletonAttribute : Attribute { diff --git a/StabilityMatrix.Core/Attributes/TransientAttribute.cs b/StabilityMatrix.Core/Attributes/TransientAttribute.cs index ede9ff4c..102fb0a8 100644 --- a/StabilityMatrix.Core/Attributes/TransientAttribute.cs +++ b/StabilityMatrix.Core/Attributes/TransientAttribute.cs @@ -3,7 +3,8 @@ namespace StabilityMatrix.Core.Attributes; -[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors), MeansImplicitUse] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +[MeansImplicitUse(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.Itself)] [AttributeUsage(AttributeTargets.Class)] public class TransientAttribute : Attribute { From 32fcf59b9160fb965cf3f3864222632e14494d0c Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 17 Dec 2024 21:16:28 -0800 Subject: [PATCH 071/297] save cm-info for OpenModelDB downloads --- .../DesignData/MockModelIndexService.cs | 5 ++++ .../Services/ModelImportService.cs | 3 ++- .../OpenModelDbBrowserViewModel.cs | 4 +++- .../CheckpointFileViewModel.cs | 23 +++++++++++++++---- .../OpenModelDbModelDetailsViewModel.cs | 11 ++++++--- .../Views/CheckpointsPage.axaml | 2 +- .../OpenModelDbModelDetailsDialog.axaml | 11 ++++----- .../Models/ConnectedModelInfo.cs | 21 +++++++++++++++++ .../Models/ConnectedModelSource.cs | 8 +++++++ .../Models/Database/LocalModelFile.cs | 2 ++ .../Services/IModelIndexService.cs | 5 ++++ .../Services/ModelIndexService.cs | 5 ++++ 12 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 StabilityMatrix.Core/Models/ConnectedModelSource.cs diff --git a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs index 0cda9486..2c2bf2d9 100644 --- a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs +++ b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs @@ -48,6 +48,11 @@ public Task> FindByHashAsync(string hashBlake3) return Task.FromResult(Enumerable.Empty()); } + public Task> FindBySha256Async(string hashSha256) + { + return Task.FromResult(Enumerable.Empty()); + } + /// public Task RemoveModelAsync(LocalModelFile model) { diff --git a/StabilityMatrix.Avalonia/Services/ModelImportService.cs b/StabilityMatrix.Avalonia/Services/ModelImportService.cs index e056f078..d8fd2d26 100644 --- a/StabilityMatrix.Avalonia/Services/ModelImportService.cs +++ b/StabilityMatrix.Avalonia/Services/ModelImportService.cs @@ -198,7 +198,8 @@ public Task DoOpenModelDbImport( modelFileName, downloadFolder, model.Images?.SelectImageAbsoluteUris().FirstOrDefault(), - configureDownload: configureDownload + configureDownload: configureDownload, + connectedModelInfo: new ConnectedModelInfo(model, resource, DateTimeOffset.Now) ); } diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs index fa892f95..e0d2935f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs @@ -91,7 +91,9 @@ private async Task OpenModelCardAsync(OpenModelDbBrowserCardViewModel? card) var vm = vmManager.Get(); vm.Model = model; - await vm.GetDialog().ShowAsync(); + var dialog = vm.GetDialog(); + dialog.MaxDialogHeight = 800; + await dialog.ShowAsync(); } /// diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs index ce61361b..332f4a5a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs @@ -137,11 +137,24 @@ private void OpenOnCivitAi() [Localizable(false)] private Task CopyModelUrl() { - return CheckpointFile.ConnectedModelInfo?.ModelId == null - ? Task.CompletedTask - : App.Clipboard.SetTextAsync( - $"https://civitai.com/models/{CheckpointFile.ConnectedModelInfo.ModelId}" - ); + if (!CheckpointFile.HasConnectedModel) + return Task.CompletedTask; + + return CheckpointFile.ConnectedModelInfo.Source switch + { + ConnectedModelSource.Civitai when CheckpointFile.ConnectedModelInfo.ModelId == null + => Task.CompletedTask, + ConnectedModelSource.Civitai when CheckpointFile.ConnectedModelInfo.ModelId != null + => App.Clipboard.SetTextAsync( + $"https://civitai.com/models/{CheckpointFile.ConnectedModelInfo.ModelId}" + ), + + ConnectedModelSource.OpenModelDb + => App.Clipboard.SetTextAsync( + $"https://openmodeldb.info/models/{CheckpointFile.ConnectedModelInfo.ModelName}" + ), + _ => Task.CompletedTask + }; } [RelayCommand] diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs index 53f6d7a3..c6330079 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs @@ -34,8 +34,8 @@ public class ModelResourceViewModel(IModelIndexService modelIndexService) public string DisplayName => $"{Resource.Platform} (.{Resource.Type} file)"; - // todo: idk - public bool IsInstalled => false; + public bool IsInstalled => + modelIndexService.FindBySha256Async(Resource.Sha256).GetAwaiter().GetResult().Any(); } [Required] @@ -81,7 +81,12 @@ private async Task ImportAsync(ModelResourceViewModel? resourceVm) sharedFolderType.GetStringValue() ); - await modelImportService.DoOpenModelDbImport(Model, resourceVm.Resource, downloadFolder); + await modelImportService.DoOpenModelDbImport( + Model, + resourceVm.Resource, + downloadFolder, + download => download.ContextAction = new ModelPostDownloadContextAction() + ); OnPrimaryButtonClick(); } diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml index 709ffaf3..00eab7b0 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml @@ -469,7 +469,7 @@ Command="{Binding CopyModelUrlCommand}" IconSource="Clipboard" IsVisible="{Binding CheckpointFile.HasConnectedModel}" - Text="Copy Link to Clipboard" /> + Text="{x:Static lang:Resources.Label_CopyLinkToClipboard}" /> - - + + @@ -202,7 +200,8 @@ Classes="accent" Command="{Binding ImportCommand}" CommandParameter="{Binding SelectedResource}" - Content="{x:Static lang:Resources.Action_Import}" /> + Content="{x:Static lang:Resources.Action_Import}" + IsVisible="{Binding !SelectedResource.IsInstalled}" /> + TextAlignment="Center" /> - - - - - - - + From 943ed7a3e127c963c3071aa623585293cd2d43e0 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 29 Dec 2024 18:54:07 -0800 Subject: [PATCH 085/297] Add RescaleCFG addon for inference --- CHANGELOG.md | 2 + StabilityMatrix.Avalonia/App.axaml | 1 + .../Controls/Inference/RescaleCfgCard.axaml | 55 +++++++++++++++++++ .../Inference/RescaleCfgCard.axaml.cs | 9 +++ .../ViewModels/Base/LoadableViewModelBase.cs | 2 + .../Inference/Modules/RescaleCfgModule.cs | 43 +++++++++++++++ .../Inference/RescaleCfgCardViewModel.cs | 22 ++++++++ .../Inference/SamplerCardViewModel.cs | 3 +- .../Api/Comfy/Nodes/ComfyNodeBuilder.cs | 6 ++ 9 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml create mode 100644 StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/RescaleCfgCardViewModel.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4531c772..91aa5851 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ 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.14.0-dev.1 +### Added +- Added Rescale CFG addon to Inference for rescaling the CFG or somethin idk ### Fixed - Fixed Inference image selector card buttons taking up the whole height of the card diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index ed93c8a9..12ade046 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -95,6 +95,7 @@ + diff --git a/StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml new file mode 100644 index 00000000..fa151286 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml.cs new file mode 100644 index 00000000..e8afa89b --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml.cs @@ -0,0 +1,9 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Injectio.Attributes; + +namespace StabilityMatrix.Avalonia.Controls; + +[RegisterTransient] +public class RescaleCfgCard : TemplatedControl { } diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs index d8bc6142..a74f4e83 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs @@ -23,6 +23,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; [JsonDerivedType(typeof(LayerDiffuseCardViewModel), LayerDiffuseCardViewModel.ModuleKey)] [JsonDerivedType(typeof(FaceDetailerViewModel), FaceDetailerViewModel.ModuleKey)] [JsonDerivedType(typeof(DiscreteModelSamplingCardViewModel), DiscreteModelSamplingCardViewModel.ModuleKey)] +[JsonDerivedType(typeof(RescaleCfgCardViewModel), RescaleCfgCardViewModel.ModuleKey)] [JsonDerivedType(typeof(FreeUModule))] [JsonDerivedType(typeof(HiresFixModule))] [JsonDerivedType(typeof(FluxHiresFixModule))] @@ -35,6 +36,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; [JsonDerivedType(typeof(FaceDetailerModule))] [JsonDerivedType(typeof(FluxGuidanceModule))] [JsonDerivedType(typeof(DiscreteModelSamplingModule))] +[JsonDerivedType(typeof(RescaleCfgModule))] public abstract class LoadableViewModelBase : ViewModelBase, IJsonLoadableState { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs new file mode 100644 index 00000000..b4a64b4c --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs @@ -0,0 +1,43 @@ +using Injectio.Attributes; +using StabilityMatrix.Avalonia.Models.Inference; +using StabilityMatrix.Avalonia.Services; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models.Api.Comfy.Nodes; + +namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; + +[ManagedService] +[RegisterTransient] +public class RescaleCfgModule : ModuleBase +{ + public RescaleCfgModule(ServiceManager vmFactory) + : base(vmFactory) + { + Title = "CFG Rescale"; + AddCards(vmFactory.Get()); + } + + protected override void OnApplyStep(ModuleApplyStepEventArgs e) + { + var vm = GetCard(); + + foreach (var modelConnections in e.Builder.Connections.Models.Values) + { + if (modelConnections.Model is not { } model) + continue; + + var rescaleCfg = e.Nodes.AddTypedNode( + new ComfyNodeBuilder.RescaleCFG + { + Name = e.Nodes.GetUniqueName("RescaleCFG"), + Model = model, + Multiplier = vm.Multiplier + } + ); + + modelConnections.Model = rescaleCfg.Output; + e.Temp.Base.Model = rescaleCfg.Output; + } + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/RescaleCfgCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/RescaleCfgCardViewModel.cs new file mode 100644 index 00000000..7d1c27f1 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/RescaleCfgCardViewModel.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using CommunityToolkit.Mvvm.ComponentModel; +using Injectio.Attributes; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.ViewModels.Inference; + +[View(typeof(RescaleCfgCard))] +[ManagedService] +[RegisterTransient] +public partial class RescaleCfgCardViewModel : LoadableViewModelBase +{ + public const string ModuleKey = "RescaleCFG"; + + [ObservableProperty] + [NotifyDataErrorInfo] + [Required] + [Range(0d, 1d)] + private double multiplier = 0.7d; +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs index c7f6bd0b..0945041d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs @@ -120,7 +120,8 @@ ServiceManager vmFactory typeof(ControlNetModule), typeof(LayerDiffuseModule), typeof(FluxGuidanceModule), - typeof(DiscreteModelSamplingModule) + typeof(DiscreteModelSamplingModule), + typeof(RescaleCfgModule) ]; }); } diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index 3f1952ab..a7a584fa 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -481,6 +481,12 @@ public record ModelSamplingDiscrete : ComfyTypedNodeBase public required bool Zsnr { get; init; } } + public record RescaleCFG : ComfyTypedNodeBase + { + public required ModelNodeConnection Model { get; init; } + public required double Multiplier { get; init; } + } + [TypedNodeOptions( Name = "CheckpointLoaderNF4", RequiredExtensions = ["https://github.com/comfyanonymous/ComfyUI_bitsandbytes_NF4"] From 9860d5e474052f9ccfe1a67811f65c021c63b6ca Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 30 Dec 2024 02:17:03 -0800 Subject: [PATCH 086/297] chagenlog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91aa5851..a3e50ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.14.0-dev.1 ### Added -- Added Rescale CFG addon to Inference for rescaling the CFG or somethin idk +- Added Rescale CFG addon to Inference ### Fixed - Fixed Inference image selector card buttons taking up the whole height of the card From c625eb8c5bca866c0a09deb1c7652cdc0d7726cb Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 31 Dec 2024 00:21:22 -0800 Subject: [PATCH 087/297] Use VAEEncode+SetLatentNoiseMask node instead of VAEEncodeForInpaint for better inpainting results --- CHANGELOG.md | 2 ++ .../Extensions/ComfyNodeBuilderExtensions.cs | 18 +++++++++++++++--- .../Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs | 6 ++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e50ae0..54375b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.14.0-dev.1 ### Added - Added Rescale CFG addon to Inference +### Changed +- Improved the quality of Inference inpainting by upgrading the workflow behind the scenes. The workflow remains the same for you — just better results! ### Fixed - Fixed Inference image selector card buttons taking up the whole height of the card diff --git a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs index d3555d52..b6685d59 100644 --- a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs +++ b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs @@ -2,6 +2,7 @@ using System.Drawing; using System.IO; using StabilityMatrix.Avalonia.Models; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Models.Api.Comfy.Nodes; namespace StabilityMatrix.Avalonia.Extensions; @@ -178,19 +179,30 @@ public static void SetupImagePrimarySourceWithMask( builder.Connections.Primary = loadImage.Output1; builder.Connections.PrimarySize = imageSize; - // Encode VAE to latent with mask, and replace primary + // new betterer inpaint builder.Connections.Primary = builder .Nodes.AddTypedNode( - new ComfyNodeBuilder.VAEEncodeForInpaint + new ComfyNodeBuilder.VAEEncode { Name = builder.Nodes.GetUniqueName("VAEEncode"), Pixels = loadImage.Output1, - Mask = loadMask.Output, Vae = builder.Connections.GetDefaultVAE() } ) .Output; + // latent noise mask for betterer inpaint + builder.Connections.Primary = builder + .Nodes.AddTypedNode( + new ComfyNodeBuilder.SetLatentNoiseMask + { + Name = builder.Nodes.GetUniqueName("SetLatentNoiseMask"), + Samples = builder.GetPrimaryAsLatent(), + Mask = loadMask.Output + } + ) + .Output; + // Add batch if selected if (builder.Connections.BatchSize > 1) { diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index a7a584fa..0433ea78 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -487,6 +487,12 @@ public record RescaleCFG : ComfyTypedNodeBase public required double Multiplier { get; init; } } + public record SetLatentNoiseMask : ComfyTypedNodeBase + { + public required LatentNodeConnection Samples { get; init; } + public required ImageMaskConnection Mask { get; init; } + } + [TypedNodeOptions( Name = "CheckpointLoaderNF4", RequiredExtensions = ["https://github.com/comfyanonymous/ComfyUI_bitsandbytes_NF4"] From e5250eeef89706c951a46e0da558a193325c12b3 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 2 Jan 2025 11:52:31 -0800 Subject: [PATCH 088/297] added swap dimensions button to inference --- CHANGELOG.md | 1 + .../Controls/Inference/SamplerCard.axaml | 181 +++++++++--------- .../Inference/SamplerCardViewModel.cs | 7 + 3 files changed, 103 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54375b18..8c0b695e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.14.0-dev.1 ### Added - Added Rescale CFG addon to Inference +- Added Swap Dimensions button between the width/height input in Inference ### Changed - Improved the quality of Inference inpainting by upgrading the workflow behind the scenes. The workflow remains the same for you — just better results! ### Fixed diff --git a/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml index 3e3079dc..52ca5a59 100644 --- a/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml @@ -1,89 +1,87 @@ - + - - - + + + - - - - + + + + + + diff --git a/StabilityMatrix.Core/Helper/RemoteModels.cs b/StabilityMatrix.Core/Helper/RemoteModels.cs index da2fbad9..ace25272 100644 --- a/StabilityMatrix.Core/Helper/RemoteModels.cs +++ b/StabilityMatrix.Core/Helper/RemoteModels.cs @@ -305,7 +305,7 @@ private static RemoteResource ControlNetCommon(string path, string sha256) HashSha256 = "660c6f5b1abae9dc498ac2d21e1347d2abdb0cf6c0c0c8576cd796491d9a6cdd", Author = "OpenAI", LicenseType = "MIT", - ContextType = SharedFolderType.CLIP, + ContextType = SharedFolderType.TextEncoders, }, new() { @@ -316,7 +316,7 @@ private static RemoteResource ControlNetCommon(string path, string sha256) HashSha256 = "6e480b09fae049a72d2a8c5fbccb8d3e92febeb233bbe9dfe7256958a9167635", Author = "Google", LicenseType = "Apache 2.0", - ContextType = SharedFolderType.CLIP, + ContextType = SharedFolderType.TextEncoders, }, new() { @@ -327,7 +327,7 @@ private static RemoteResource ControlNetCommon(string path, string sha256) HashSha256 = "7d330da4816157540d6bb7838bf63a0f02f573fc48ca4d8de34bb0cbfd514f09", Author = "Google", LicenseType = "Apache 2.0", - ContextType = SharedFolderType.CLIP, + ContextType = SharedFolderType.TextEncoders, }, new() { @@ -338,7 +338,7 @@ private static RemoteResource ControlNetCommon(string path, string sha256) HashSha256 = "c3355d30191f1f066b26d93fba017ae9809dce6c627dda5f6a66eaa651204f68", Author = "Google", LicenseType = "Apache 2.0", - ContextType = SharedFolderType.CLIP, + ContextType = SharedFolderType.TextEncoders, }, new() { @@ -349,7 +349,7 @@ private static RemoteResource ControlNetCommon(string path, string sha256) HashSha256 = "7b8850f1961e1cf8a77cca4c964a358d303f490833c6c087d0cff4b2f99db2af", Author = "Google", LicenseType = "Apache 2.0", - ContextType = SharedFolderType.CLIP, + ContextType = SharedFolderType.TextEncoders, } ]; @@ -367,7 +367,7 @@ private static RemoteResource ControlNetCommon(string path, string sha256) HashSha256 = "64a7ef761bfccbadbaa3da77366aac4185a6c58fa5de5f589b42a65bcc21f161", Author = "OpenAI", LicenseType = "MIT", - ContextType = SharedFolderType.InvokeClipVision, + ContextType = SharedFolderType.ClipVision, } ]; diff --git a/StabilityMatrix.Core/Helper/SharedFolders.cs b/StabilityMatrix.Core/Helper/SharedFolders.cs index d0c5e067..7a77b723 100644 --- a/StabilityMatrix.Core/Helper/SharedFolders.cs +++ b/StabilityMatrix.Core/Helper/SharedFolders.cs @@ -1,5 +1,6 @@ using Injectio.Attributes; using NLog; +using OneOf.Types; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper.Factory; using StabilityMatrix.Core.Models; @@ -18,6 +19,18 @@ public class SharedFolders(ISettingsManager settingsManager, IPackageFactory pac { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + // mapping is old:new + private static readonly Dictionary LegacySharedFolderMapping = + new() + { + { "CLIP", "TextEncoders" }, + { "Unet", "DiffusionModels" }, + { "InvokeClipVision", "ClipVision" }, + { "InvokeIpAdapters15", "IpAdapters15" }, + { "InvokeIpAdaptersXl", "IpAdaptersXl" }, + { "TextualInversion", "Embeddings" } + }; + public bool IsDisposed { get; private set; } /// @@ -237,6 +250,38 @@ public static void SetupSharedModelFolders(DirectoryPath rootModelsDir) segmDir.Create(); } } + + MigrateOldSharedFolderPaths(rootModelsDir); + } + + private static void MigrateOldSharedFolderPaths(DirectoryPath rootModelsDir) + { + foreach (var (legacyFolderName, newFolderName) in LegacySharedFolderMapping) + { + var fullPath = rootModelsDir.JoinDir(legacyFolderName); + if (!fullPath.Exists) + continue; + + foreach (var file in fullPath.EnumerateFiles(searchOption: SearchOption.AllDirectories)) + { + var relativePath = file.RelativeTo(fullPath); + var newPath = rootModelsDir.JoinFile(newFolderName, relativePath); + newPath.Directory?.Create(); + file.MoveTo(newPath); + } + } + + // delete the old directories *only if they're empty* + foreach ( + var fullPath in from legacyFolderName in LegacySharedFolderMapping.Keys + select rootModelsDir.JoinDir(legacyFolderName) into fullPath + where fullPath.Exists + where !fullPath.EnumerateFiles(searchOption: SearchOption.AllDirectories).Any() + select fullPath + ) + { + fullPath.Delete(true); + } } public async ValueTask DisposeAsync() diff --git a/StabilityMatrix.Core/Models/Api/CivitModelType.cs b/StabilityMatrix.Core/Models/Api/CivitModelType.cs index 1d30cf30..1bbf160c 100644 --- a/StabilityMatrix.Core/Models/Api/CivitModelType.cs +++ b/StabilityMatrix.Core/Models/Api/CivitModelType.cs @@ -14,7 +14,7 @@ public enum CivitModelType [ConvertTo(SharedFolderType.StableDiffusion)] Checkpoint, - [ConvertTo(SharedFolderType.TextualInversion)] + [ConvertTo(SharedFolderType.Embeddings)] TextualInversion, [ConvertTo(SharedFolderType.Hypernetwork)] diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index a6a302e8..224cb20a 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -58,7 +58,7 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.VAE] = ["models/VAE"], [SharedFolderType.DeepDanbooru] = ["models/deepbooru"], [SharedFolderType.Karlo] = ["models/karlo"], - [SharedFolderType.TextualInversion] = ["embeddings"], + [SharedFolderType.Embeddings] = ["embeddings"], [SharedFolderType.Hypernetwork] = ["models/hypernetworks"], [SharedFolderType.ControlNet] = ["models/controlnet/ControlNet"], [SharedFolderType.Codeformer] = ["models/Codeformer"], @@ -66,11 +66,11 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.AfterDetailer] = ["models/adetailer"], [SharedFolderType.T2IAdapter] = ["models/controlnet/T2IAdapter"], [SharedFolderType.IpAdapter] = ["models/controlnet/IpAdapter"], - [SharedFolderType.InvokeIpAdapters15] = ["models/controlnet/DiffusersIpAdapters"], - [SharedFolderType.InvokeIpAdaptersXl] = ["models/controlnet/DiffusersIpAdaptersXL"], + [SharedFolderType.IpAdapters15] = ["models/controlnet/DiffusersIpAdapters"], + [SharedFolderType.IpAdaptersXl] = ["models/controlnet/DiffusersIpAdaptersXL"], [SharedFolderType.SVD] = ["models/svd"], - [SharedFolderType.CLIP] = ["models/text_encoder"], - [SharedFolderType.Unet] = ["models/Stable-diffusion/unet"], + [SharedFolderType.TextEncoders] = ["models/text_encoder"], + [SharedFolderType.DiffusionModels] = ["models/Stable-diffusion/unet"], }; public override Dictionary>? SharedOutputFolders => diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index b30b8f4a..74c49162 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -53,9 +53,9 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.StableDiffusion] = ["models/checkpoints"], [SharedFolderType.Diffusers] = ["models/diffusers"], [SharedFolderType.Lora] = ["models/loras"], - [SharedFolderType.CLIP] = ["models/clip"], - [SharedFolderType.InvokeClipVision] = ["models/clip_vision"], - [SharedFolderType.TextualInversion] = ["models/embeddings"], + [SharedFolderType.TextEncoders] = ["models/clip"], + [SharedFolderType.ClipVision] = ["models/clip_vision"], + [SharedFolderType.Embeddings] = ["models/embeddings"], [SharedFolderType.VAE] = ["models/vae"], [SharedFolderType.ApproxVAE] = ["models/vae_approx"], [SharedFolderType.ControlNet] = ["models/controlnet/ControlNet"], @@ -63,13 +63,13 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.ESRGAN] = ["models/upscale_models"], [SharedFolderType.Hypernetwork] = ["models/hypernetworks"], [SharedFolderType.IpAdapter] = ["models/ipadapter/base"], - [SharedFolderType.InvokeIpAdapters15] = ["models/ipadapter/sd15"], - [SharedFolderType.InvokeIpAdaptersXl] = ["models/ipadapter/sdxl"], + [SharedFolderType.IpAdapters15] = ["models/ipadapter/sd15"], + [SharedFolderType.IpAdaptersXl] = ["models/ipadapter/sdxl"], [SharedFolderType.T2IAdapter] = ["models/controlnet/T2IAdapter"], [SharedFolderType.PromptExpansion] = ["models/prompt_expansion"], [SharedFolderType.Ultralytics] = ["models/ultralytics"], [SharedFolderType.Sams] = ["models/sams"], - [SharedFolderType.Unet] = ["models/diffusion_models"] + [SharedFolderType.DiffusionModels] = ["models/diffusion_models"] }; public override Dictionary>? SharedOutputFolders => diff --git a/StabilityMatrix.Core/Models/Packages/FluxGym.cs b/StabilityMatrix.Core/Models/Packages/FluxGym.cs index 073b7ba5..0b229f5c 100644 --- a/StabilityMatrix.Core/Models/Packages/FluxGym.cs +++ b/StabilityMatrix.Core/Models/Packages/FluxGym.cs @@ -51,12 +51,12 @@ IPrerequisiteHelper prerequisiteHelper [ new SharedFolderLayoutRule { - SourceTypes = [SharedFolderType.CLIP], + SourceTypes = [SharedFolderType.TextEncoders], TargetRelativePaths = ["models/clip"] }, new SharedFolderLayoutRule { - SourceTypes = [SharedFolderType.Unet], + SourceTypes = [SharedFolderType.DiffusionModels], TargetRelativePaths = ["models/unet"] }, new SharedFolderLayoutRule diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs index 3a0a3481..4d3fef43 100644 --- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs @@ -173,7 +173,7 @@ IPrerequisiteHelper prerequisiteHelper }, new SharedFolderLayoutRule { - SourceTypes = [SharedFolderType.CLIP], + SourceTypes = [SharedFolderType.TextEncoders], TargetRelativePaths = ["models/clip"] }, new SharedFolderLayoutRule @@ -193,7 +193,7 @@ IPrerequisiteHelper prerequisiteHelper }, new SharedFolderLayoutRule { - SourceTypes = [SharedFolderType.TextualInversion], + SourceTypes = [SharedFolderType.Embeddings], TargetRelativePaths = ["models/embeddings"], ConfigDocumentPaths = ["path_embeddings"] }, @@ -217,7 +217,7 @@ IPrerequisiteHelper prerequisiteHelper }, new SharedFolderLayoutRule { - SourceTypes = [SharedFolderType.InvokeClipVision], + SourceTypes = [SharedFolderType.ClipVision], TargetRelativePaths = ["models/clip_vision"], ConfigDocumentPaths = ["path_clip_vision"] }, diff --git a/StabilityMatrix.Core/Models/Packages/FooocusMre.cs b/StabilityMatrix.Core/Models/Packages/FooocusMre.cs index 7adef809..274c57e6 100644 --- a/StabilityMatrix.Core/Models/Packages/FooocusMre.cs +++ b/StabilityMatrix.Core/Models/Packages/FooocusMre.cs @@ -81,8 +81,8 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.StableDiffusion] = new[] { "models/checkpoints" }, [SharedFolderType.Diffusers] = new[] { "models/diffusers" }, [SharedFolderType.Lora] = new[] { "models/loras" }, - [SharedFolderType.CLIP] = new[] { "models/clip" }, - [SharedFolderType.TextualInversion] = new[] { "models/embeddings" }, + [SharedFolderType.TextEncoders] = new[] { "models/clip" }, + [SharedFolderType.Embeddings] = new[] { "models/embeddings" }, [SharedFolderType.VAE] = new[] { "models/vae" }, [SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" }, [SharedFolderType.ControlNet] = new[] { "models/controlnet" }, diff --git a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs index 51ca659e..39fa904c 100644 --- a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs +++ b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs @@ -59,20 +59,17 @@ IPrerequisiteHelper prerequisiteHelper { [SharedFolderType.StableDiffusion] = [Path.Combine(RelativeRootPath, "autoimport", "main")], [SharedFolderType.Lora] = [Path.Combine(RelativeRootPath, "autoimport", "lora")], - [SharedFolderType.TextualInversion] = [Path.Combine(RelativeRootPath, "autoimport", "embedding")], + [SharedFolderType.Embeddings] = [Path.Combine(RelativeRootPath, "autoimport", "embedding")], [SharedFolderType.ControlNet] = [Path.Combine(RelativeRootPath, "autoimport", "controlnet")], - [SharedFolderType.InvokeIpAdapters15] = + [SharedFolderType.IpAdapters15] = [ Path.Combine(RelativeRootPath, "models", "sd-1", "ip_adapter") ], - [SharedFolderType.InvokeIpAdaptersXl] = + [SharedFolderType.IpAdaptersXl] = [ Path.Combine(RelativeRootPath, "models", "sdxl", "ip_adapter") ], - [SharedFolderType.InvokeClipVision] = - [ - Path.Combine(RelativeRootPath, "models", "any", "clip_vision") - ], + [SharedFolderType.ClipVision] = [Path.Combine(RelativeRootPath, "models", "any", "clip_vision")], [SharedFolderType.T2IAdapter] = [Path.Combine(RelativeRootPath, "autoimport", "t2i_adapter")] }; diff --git a/StabilityMatrix.Core/Models/Packages/Sdfx.cs b/StabilityMatrix.Core/Models/Packages/Sdfx.cs index 0a11596b..c1d7d83b 100644 --- a/StabilityMatrix.Core/Models/Packages/Sdfx.cs +++ b/StabilityMatrix.Core/Models/Packages/Sdfx.cs @@ -46,9 +46,9 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.StableDiffusion] = new[] { "data/models/checkpoints" }, [SharedFolderType.Diffusers] = new[] { "data/models/diffusers" }, [SharedFolderType.Lora] = new[] { "data/models/loras" }, - [SharedFolderType.CLIP] = new[] { "data/models/clip" }, - [SharedFolderType.InvokeClipVision] = new[] { "data/models/clip_vision" }, - [SharedFolderType.TextualInversion] = new[] { "data/models/embeddings" }, + [SharedFolderType.TextEncoders] = new[] { "data/models/clip" }, + [SharedFolderType.ClipVision] = new[] { "data/models/clip_vision" }, + [SharedFolderType.Embeddings] = new[] { "data/models/embeddings" }, [SharedFolderType.VAE] = new[] { "data/models/vae" }, [SharedFolderType.ApproxVAE] = new[] { "data/models/vae_approx" }, [SharedFolderType.ControlNet] = new[] { "data/models/controlnet/ControlNet" }, @@ -56,8 +56,8 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.ESRGAN] = new[] { "data/models/upscale_models" }, [SharedFolderType.Hypernetwork] = new[] { "data/models/hypernetworks" }, [SharedFolderType.IpAdapter] = new[] { "data/models/ipadapter/base" }, - [SharedFolderType.InvokeIpAdapters15] = new[] { "data/models/ipadapter/sd15" }, - [SharedFolderType.InvokeIpAdaptersXl] = new[] { "data/models/ipadapter/sdxl" }, + [SharedFolderType.IpAdapters15] = new[] { "data/models/ipadapter/sd15" }, + [SharedFolderType.IpAdaptersXl] = new[] { "data/models/ipadapter/sdxl" }, [SharedFolderType.T2IAdapter] = new[] { "data/models/controlnet/T2IAdapter" }, [SharedFolderType.PromptExpansion] = new[] { "data/models/prompt_expansion" } }; diff --git a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs index cab3947d..02623989 100644 --- a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs +++ b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs @@ -57,7 +57,7 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.VAE] = new[] { "models/VAE" }, [SharedFolderType.DeepDanbooru] = new[] { "models/deepbooru" }, [SharedFolderType.Karlo] = new[] { "models/karlo" }, - [SharedFolderType.TextualInversion] = new[] { "embeddings" }, + [SharedFolderType.Embeddings] = new[] { "embeddings" }, [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, [SharedFolderType.ControlNet] = new[] { "models/ControlNet" }, [SharedFolderType.Codeformer] = new[] { "models/Codeformer" }, diff --git a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs index 6750b138..1a4f7988 100644 --- a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs +++ b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs @@ -124,9 +124,9 @@ await RebuildDotnetProject(installedPackage.FullPath, csprojName, null) [SharedFolderType.StableDiffusion] = ["Models/Stable-Diffusion"], [SharedFolderType.Lora] = ["Models/Lora"], [SharedFolderType.VAE] = ["Models/VAE"], - [SharedFolderType.TextualInversion] = ["Models/Embeddings"], + [SharedFolderType.Embeddings] = ["Models/Embeddings"], [SharedFolderType.ControlNet] = ["Models/controlnet"], - [SharedFolderType.InvokeClipVision] = ["Models/clip_vision"] + [SharedFolderType.ClipVision] = ["Models/clip_vision"] }; public override Dictionary> SharedOutputFolders => new() { [SharedOutputType.Text2Img] = [OutputFolderName] }; @@ -230,7 +230,7 @@ await prerequisiteHelper ), SDEmbeddingFolder = Path.Combine( settingsManager.ModelsDirectory, - SharedFolderType.TextualInversion.ToString() + SharedFolderType.Embeddings.ToString() ), SDControlNetsFolder = Path.Combine( settingsManager.ModelsDirectory, @@ -238,7 +238,7 @@ await prerequisiteHelper ), SDClipVisionFolder = Path.Combine( settingsManager.ModelsDirectory, - SharedFolderType.InvokeClipVision.ToString() + SharedFolderType.ClipVision.ToString() ) }; } @@ -503,7 +503,7 @@ private Task SetupModelFoldersConfig(DirectoryPath installDirectory) ); paths.Set( "SDEmbeddingFolder", - Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.TextualInversion.ToString()) + Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.Embeddings.ToString()) ); paths.Set( "SDControlNetsFolder", @@ -511,7 +511,7 @@ private Task SetupModelFoldersConfig(DirectoryPath installDirectory) ); paths.Set( "SDClipVisionFolder", - Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.InvokeClipVision.ToString()) + Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.ClipVision.ToString()) ); section.Set("Paths", paths); section.SaveToFile(settingsPath); @@ -529,7 +529,7 @@ private Task SetupModelFoldersConfig(DirectoryPath installDirectory) SDVAEFolder = Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.VAE.ToString()), SDEmbeddingFolder = Path.Combine( settingsManager.ModelsDirectory, - SharedFolderType.TextualInversion.ToString() + SharedFolderType.Embeddings.ToString() ), SDControlNetsFolder = Path.Combine( settingsManager.ModelsDirectory, @@ -537,7 +537,7 @@ private Task SetupModelFoldersConfig(DirectoryPath installDirectory) ), SDClipVisionFolder = Path.Combine( settingsManager.ModelsDirectory, - SharedFolderType.InvokeClipVision.ToString() + SharedFolderType.ClipVision.ToString() ) }; diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index 5d81bd4e..6307a763 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -60,7 +60,7 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" }, [SharedFolderType.Diffusers] = new[] { "models/Diffusers" }, [SharedFolderType.VAE] = new[] { "models/VAE" }, - [SharedFolderType.TextualInversion] = new[] { "models/embeddings" }, + [SharedFolderType.Embeddings] = new[] { "models/embeddings" }, [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, [SharedFolderType.Codeformer] = new[] { "models/Codeformer" }, [SharedFolderType.GFPGAN] = new[] { "models/GFPGAN" }, @@ -70,7 +70,7 @@ IPrerequisiteHelper prerequisiteHelper [SharedFolderType.ScuNET] = new[] { "models/ScuNET" }, [SharedFolderType.SwinIR] = new[] { "models/SwinIR" }, [SharedFolderType.LDSR] = new[] { "models/LDSR" }, - [SharedFolderType.CLIP] = new[] { "models/CLIP" }, + [SharedFolderType.TextEncoders] = new[] { "models/CLIP" }, [SharedFolderType.Lora] = new[] { "models/Lora" }, [SharedFolderType.LyCORIS] = new[] { "models/LyCORIS" }, [SharedFolderType.ControlNet] = new[] { "models/ControlNet" } diff --git a/StabilityMatrix.Core/Models/Packages/VoltaML.cs b/StabilityMatrix.Core/Models/Packages/VoltaML.cs index dcf57929..acc2db9c 100644 --- a/StabilityMatrix.Core/Models/Packages/VoltaML.cs +++ b/StabilityMatrix.Core/Models/Packages/VoltaML.cs @@ -40,7 +40,7 @@ IPrerequisiteHelper prerequisiteHelper { [SharedFolderType.StableDiffusion] = new[] { "data/models" }, [SharedFolderType.Lora] = new[] { "data/lora" }, - [SharedFolderType.TextualInversion] = new[] { "data/textual-inversion" }, + [SharedFolderType.Embeddings] = new[] { "data/textual-inversion" }, }; public override Dictionary>? SharedOutputFolders => diff --git a/StabilityMatrix.Core/Models/Settings/TeachingTip.cs b/StabilityMatrix.Core/Models/Settings/TeachingTip.cs index 643aca2a..c22a8713 100644 --- a/StabilityMatrix.Core/Models/Settings/TeachingTip.cs +++ b/StabilityMatrix.Core/Models/Settings/TeachingTip.cs @@ -16,6 +16,7 @@ public record TeachingTip(string Value) : StringValue(Value) public static TeachingTip WebUiButtonMovedTip => new("WebUiButtonMovedTip"); public static TeachingTip InferencePromptHelpButtonTip => new("InferencePromptHelpButtonTip"); public static TeachingTip LykosAccountMigrateTip => new("LykosAccountMigrateTip"); + public static TeachingTip SharedFolderMigrationTip => new("SharedFolderMigrationTip"); /// public override string ToString() diff --git a/StabilityMatrix.Core/Models/SharedFolderType.cs b/StabilityMatrix.Core/Models/SharedFolderType.cs index 564b71a5..4978ad3f 100644 --- a/StabilityMatrix.Core/Models/SharedFolderType.cs +++ b/StabilityMatrix.Core/Models/SharedFolderType.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using StabilityMatrix.Core.Extensions; namespace StabilityMatrix.Core.Models; @@ -10,10 +9,12 @@ public enum SharedFolderType : ulong { Unknown = 0, - [Description("Base Models")] + [Extensions.Description("Checkpoints")] StableDiffusion = 1 << 0, Lora = 1 << 1, LyCORIS = 1 << 2, + + [Extensions.Description("Upscalers (ESRGAN)")] ESRGAN = 1 << 3, GFPGAN = 1 << 4, BSRGAN = 1 << 5, @@ -25,24 +26,24 @@ public enum SharedFolderType : ulong ApproxVAE = 1 << 11, Karlo = 1 << 12, DeepDanbooru = 1 << 13, - - [Description("TextualInversion (Embeddings)")] - TextualInversion = 1 << 14, + Embeddings = 1 << 14, Hypernetwork = 1 << 15, ControlNet = 1 << 16, LDSR = 1 << 17, - CLIP = 1 << 18, + TextEncoders = 1 << 18, ScuNET = 1 << 19, GLIGEN = 1 << 20, AfterDetailer = 1 << 21, IpAdapter = 1 << 22, T2IAdapter = 1 << 23, - InvokeIpAdapters15 = 1 << 24, - InvokeIpAdaptersXl = 1 << 25, - InvokeClipVision = 1 << 26, + IpAdapters15 = 1 << 24, + IpAdaptersXl = 1 << 25, + ClipVision = 1 << 26, SVD = 1 << 27, Ultralytics = 1 << 28, Sams = 1 << 29, PromptExpansion = 1 << 30, - Unet = 1ul << 31, + + [Extensions.Description("Diffusion Models (UNet-only)")] + DiffusionModels = 1ul << 31, } diff --git a/StabilityMatrix.Core/Models/Tokens/PromptExtraNetworkType.cs b/StabilityMatrix.Core/Models/Tokens/PromptExtraNetworkType.cs index 14d04517..46b2fe24 100644 --- a/StabilityMatrix.Core/Models/Tokens/PromptExtraNetworkType.cs +++ b/StabilityMatrix.Core/Models/Tokens/PromptExtraNetworkType.cs @@ -2,14 +2,15 @@ namespace StabilityMatrix.Core.Models.Tokens; - [Flags] public enum PromptExtraNetworkType { [ConvertTo(SharedFolderType.Lora)] Lora = 1 << 0, + [ConvertTo(SharedFolderType.LyCORIS)] LyCORIS = 1 << 1, - [ConvertTo(SharedFolderType.TextualInversion)] + + [ConvertTo(SharedFolderType.Embeddings)] Embedding = 1 << 2 } diff --git a/StabilityMatrix.Tests/Models/SharedFoldersTests.cs b/StabilityMatrix.Tests/Models/SharedFoldersTests.cs index 8b44241c..b090d183 100644 --- a/StabilityMatrix.Tests/Models/SharedFoldersTests.cs +++ b/StabilityMatrix.Tests/Models/SharedFoldersTests.cs @@ -16,7 +16,7 @@ public class SharedFoldersTests { [SharedFolderType.StableDiffusion] = "models/Stable-diffusion", [SharedFolderType.ESRGAN] = "models/ESRGAN", - [SharedFolderType.TextualInversion] = "embeddings", + [SharedFolderType.Embeddings] = "embeddings", }; [TestInitialize] @@ -41,7 +41,7 @@ private void CreateSampleJunctions() { [SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" }, [SharedFolderType.ESRGAN] = new[] { "models/ESRGAN" }, - [SharedFolderType.TextualInversion] = new[] { "embeddings" }, + [SharedFolderType.Embeddings] = new[] { "embeddings" }, }; SharedFolders .UpdateLinksForPackage(definitions, TempModelsFolder, TempPackageFolder) @@ -60,10 +60,7 @@ public void SetupLinks_CreatesJunctions() var packagePath = Path.Combine(TempPackageFolder, relativePath); var modelFolder = Path.Combine(TempModelsFolder, folderType.GetStringValue()); // Should exist and be a junction - Assert.IsTrue( - Directory.Exists(packagePath), - $"Package folder {packagePath} does not exist." - ); + Assert.IsTrue(Directory.Exists(packagePath), $"Package folder {packagePath} does not exist."); var info = new DirectoryInfo(packagePath); Assert.IsTrue( info.Attributes.HasFlag(FileAttributes.ReparsePoint), @@ -108,10 +105,7 @@ public void SetupLinks_CanDeleteJunctions() // Now delete the junction Directory.Delete(packagePath, false); - Assert.IsFalse( - Directory.Exists(packagePath), - $"Package folder {packagePath} should not exist." - ); + Assert.IsFalse(Directory.Exists(packagePath), $"Package folder {packagePath} should not exist."); // The file should still exist in the model folder Assert.IsTrue( From 1af50f671b0a1e68511ca5ce49ef665810d5c843 Mon Sep 17 00:00:00 2001 From: jt Date: Sun, 30 Mar 2025 16:08:08 -0700 Subject: [PATCH 185/297] Moved migration tip to main window loaded & maybe fix white boxes on mac/linux --- StabilityMatrix.Avalonia/Program.cs | 9 +++++++++ .../ViewModels/CheckpointsPageViewModel.cs | 12 ++++-------- .../ViewModels/MainWindowViewModel.cs | 15 +++++++++++++++ .../Models/Settings/TeachingTip.cs | 1 + 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/StabilityMatrix.Avalonia/Program.cs b/StabilityMatrix.Avalonia/Program.cs index b98696da..cb73078b 100644 --- a/StabilityMatrix.Avalonia/Program.cs +++ b/StabilityMatrix.Avalonia/Program.cs @@ -184,6 +184,15 @@ public static AppBuilder BuildAvaloniaApp() var app = AppBuilder.Configure().UsePlatformDetect().WithInterFont().LogToTrace(); + if (Compat.IsLinux) + { + app = app.With(new X11PlatformOptions { OverlayPopups = true }); + } + else if (Compat.IsMacOS) + { + app = app.With(new AvaloniaNativePlatformOptions { OverlayPopups = true }); + } + if (Args.UseOpenGlRendering) { app = app.With( diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs index d2edc8da..5d7c4b2d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs @@ -492,7 +492,7 @@ public override async Task OnLoadedAsync() if (Design.IsDesignMode) return; - await ShowMigrationNoticeIfNecessaryAsync(); + await ShowFolderMapTipIfNecessaryAsync(); } public void ClearSearchQuery() @@ -1257,19 +1257,15 @@ private bool FilterCategories(CheckpointCategory category) return !HideEmptyRootCategories || category is { Count: > 0 }; } - private async Task ShowMigrationNoticeIfNecessaryAsync() + private async Task ShowFolderMapTipIfNecessaryAsync() { - if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.SharedFolderMigrationTip)) + if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.FolderMapTip)) return; - var migrationNotice = DialogHelper.CreateMarkdownDialog(MarkdownSnippets.SharedFolderMigration); - migrationNotice.CloseButtonText = Resources.Action_OK; - await migrationNotice.ShowAsync(); - var folderReference = DialogHelper.CreateMarkdownDialog(MarkdownSnippets.SMFolderMap); folderReference.CloseButtonText = Resources.Action_OK; await folderReference.ShowAsync(); - settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.SharedFolderMigrationTip)); + settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.FolderMapTip)); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index e0d0573b..7212a8e8 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs @@ -11,6 +11,7 @@ using FluentAvalonia.UI.Media.Animation; using NLog; using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; @@ -355,6 +356,8 @@ await Dispatcher.UIThread.InvokeAsync(async () => { Logger.Error(ex, "Error during account migration notice check"); }); + + await ShowMigrationTipIfNecessaryAsync(); } private void PreloadPages() @@ -504,4 +507,16 @@ public async Task ShowUpdateDialog() await viewModel.Preload(); await dialog.ShowAsync(); } + + private async Task ShowMigrationTipIfNecessaryAsync() + { + if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.SharedFolderMigrationTip)) + return; + + var folderReference = DialogHelper.CreateMarkdownDialog(MarkdownSnippets.SharedFolderMigration); + folderReference.CloseButtonText = Resources.Action_OK; + await folderReference.ShowAsync(); + + settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.SharedFolderMigrationTip)); + } } diff --git a/StabilityMatrix.Core/Models/Settings/TeachingTip.cs b/StabilityMatrix.Core/Models/Settings/TeachingTip.cs index c22a8713..1b666056 100644 --- a/StabilityMatrix.Core/Models/Settings/TeachingTip.cs +++ b/StabilityMatrix.Core/Models/Settings/TeachingTip.cs @@ -17,6 +17,7 @@ public record TeachingTip(string Value) : StringValue(Value) public static TeachingTip InferencePromptHelpButtonTip => new("InferencePromptHelpButtonTip"); public static TeachingTip LykosAccountMigrateTip => new("LykosAccountMigrateTip"); public static TeachingTip SharedFolderMigrationTip => new("SharedFolderMigrationTip"); + public static TeachingTip FolderMapTip => new("FolderMapTip"); /// public override string ToString() From 225fe8e307bca3a5194920c8968095d2d10ad7a5 Mon Sep 17 00:00:00 2001 From: jt Date: Sun, 30 Mar 2025 16:15:11 -0700 Subject: [PATCH 186/297] chagenlog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96091bc6..8f3fa1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Fixed window disappearing on macOS when the saved window size is very small - Fixed ComfyUI-Zluda not being recognized as an option for Inference or SwarmUI (for real this time) - Fixed missing base model options in the Metadata Editor +- Fixed large white boxes appearing when tooltips are visible on macOS/Linux ## v2.13.4 ### Added From 13a4111e0cc3a53b2b6e53ed2d335c215b4d11ec Mon Sep 17 00:00:00 2001 From: jt Date: Mon, 31 Mar 2025 21:32:35 -0700 Subject: [PATCH 187/297] Do the ROCmLibs dance & HIP SDK upgrade & show a warning for installs that might require admin privileges --- CHANGELOG.md | 3 +- .../Helpers/UnixPrerequisiteHelper.cs | 1 + .../Helpers/WindowsElevated.cs | 2 +- .../Helpers/WindowsPrerequisiteHelper.cs | 143 ++++++++++++++++-- .../PackageInstallDetailViewModel.cs | 23 +++ StabilityMatrix.Core/Helper/ArchiveHelper.cs | 8 +- .../Helper/IPrerequisiteHelper.cs | 1 + .../Models/Packages/BasePackage.cs | 2 + .../Models/Packages/ComfyZluda.cs | 27 +++- .../Models/Packages/ForgeAmdGpu.cs | 27 +++- 10 files changed, 210 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3fa1d6..75acd90b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Changed the names of some of the shared model folders to better reflect their contents - Improved window state handling - Improved Checkpoint Manager memory usage (thanks to @FireGeek for the profiling assistance!) -- (Internal) Upgraded FluentAvalonia to 2.3.0 +- Upgraded HIP SDK installs to 6.2.4 for ComfyUI-Zluda and AMDGPU-Forge +- (Internal) Upgraded FluentAvalonia to 2.3.0 ### Fixed - Fixed RTX 5000-series GPU detection in certain cases - Fixed Image Viewer animation loader keeping file handles open, which resolves 2 different issues (OSes are fun): diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs index f4ac26f9..781c6c23 100644 --- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs @@ -60,6 +60,7 @@ IPyRunner pyRunner private bool? isGitInstalled; public bool IsVcBuildToolsInstalled => false; + public bool IsHipSdkInstalled => false; private async Task CheckIsGitInstalled() { diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs b/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs index 01077289..7dbe9cd7 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs @@ -20,7 +20,7 @@ public static async Task MoveFiles(params (string sourcePath, string target process.StartInfo.FileName = "cmd.exe"; process.StartInfo.Arguments = $"/c {args}"; process.StartInfo.UseShellExecute = true; - process.StartInfo.CreateNoWindow = true; + process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.StartInfo.Verb = "runas"; process.Start(); diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index 162a9a56..f40aace7 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; +using System.Diagnostics; using System.Runtime.Versioning; -using System.Threading.Tasks; using Microsoft.Win32; using NLog; using StabilityMatrix.Core.Exceptions; using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -45,7 +42,8 @@ IPyRunner pyRunner private const string CppBuildToolsUrl = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; - private const string HipSdkDownloadUrl = "https://cdn.lykos.ai/AMD-HIP-SDK.exe"; + private const string HipSdkDownloadUrl = + "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-24.Q4-Win10-Win11-For-HIP.exe"; private string HomeDir => settingsManager.LibraryDir; @@ -88,11 +86,12 @@ IPyRunner pyRunner private string HipSdkDownloadPath => Path.Combine(AssetsDir, "AMD-HIP-SDK.exe"); private string HipInstalledPath => - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "AMD", "ROCm", "5.7"); + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "AMD", "ROCm", "6.2"); public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin"); public bool IsPythonInstalled => File.Exists(PythonDllPath); public bool IsVcBuildToolsInstalled => Directory.Exists(VcBuildToolsExistsPath); + public bool IsHipSdkInstalled => Directory.Exists(HipInstalledPath); public async Task RunGit( ProcessArgs args, @@ -165,6 +164,11 @@ public async Task InstallPackageRequirements( { await UnpackResourcesIfNecessary(progress); + if (prerequisites.Contains(PackagePrerequisite.HipSdk)) + { + await InstallHipSdkIfNecessary(progress); + } + if (prerequisites.Contains(PackagePrerequisite.Python310)) { await InstallPythonIfNecessary(progress); @@ -200,11 +204,6 @@ public async Task InstallPackageRequirements( { await InstallVcBuildToolsIfNecessary(progress); } - - if (prerequisites.Contains(PackagePrerequisite.HipSdk)) - { - await InstallHipSdkIfNecessary(progress); - } } public async Task InstallAllIfNecessary(IProgress? progress = null) @@ -570,8 +569,11 @@ await downloadService.DownloadToFileAsync( [SupportedOSPlatform("windows")] public async Task InstallHipSdkIfNecessary(IProgress? progress = null) { - if (Directory.Exists(HipInstalledPath)) + if (IsHipSdkInstalled) + { + await PatchHipSdkIfNecessary(progress); return; + } await downloadService.DownloadToFileAsync(HipSdkDownloadUrl, HipSdkDownloadPath, progress: progress); Logger.Info("Downloaded & installing HIP SDK"); @@ -598,6 +600,8 @@ public async Task InstallHipSdkIfNecessary(IProgress? progress = { await process.WaitForExitAsync(); } + + await PatchHipSdkIfNecessary(progress); } public async Task RunDotnet( @@ -689,4 +693,115 @@ private async Task UnzipGit(IProgress? progress = null) File.Delete(PortableGitDownloadPath); } + + private async Task PatchHipSdkIfNecessary(IProgress? progress) + { + var theGpu = + settingsManager.Settings.PreferredGpu + ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.Name != null && x.Name.Contains("AMD")); + + if (theGpu?.Name is null) + return; + var downloadUrl = GetDownloadUrlFromGpuName(theGpu.Name); + if (downloadUrl is null) + return; + + progress?.Report(new ProgressReport(-1, "Patching ROCm for your GPU", isIndeterminate: true)); + + var rocmLibsDownloadPath = new FilePath(AssetsDir, "rocmLibs.7z"); + await downloadService.DownloadToFileAsync(downloadUrl, rocmLibsDownloadPath, progress: progress); + + var rocmLibsExtractPath = new DirectoryPath(AssetsDir, "rocmLibs"); + await ArchiveHelper.Extract7Z(rocmLibsDownloadPath, rocmLibsExtractPath, progress); + + var hipInstalledPath = new DirectoryPath(HipInstalledPath); + var librarySourceDir = rocmLibsExtractPath.JoinDir("library"); + var libraryDestDir = hipInstalledPath.JoinDir("bin", "rocblas", "library"); + + List<(string sourcePath, string destPath)> listOfMoves = []; + foreach (var file in librarySourceDir.EnumerateFiles(searchOption: SearchOption.AllDirectories)) + { + var relativePath = file.RelativeTo(librarySourceDir); + var newPath = libraryDestDir.JoinFile(relativePath); + newPath.Directory?.Create(); + listOfMoves.Add((file.FullPath, newPath.FullPath)); + } + + var rocblasSource = rocmLibsExtractPath.JoinFile("rocblas.dll"); + var rocblasDest = hipInstalledPath.JoinDir("bin").JoinFile("rocblas.dll"); + if (rocblasSource.Exists) + { + listOfMoves.Add((rocblasSource.FullPath, rocblasDest.FullPath)); + } + + progress?.Report(new ProgressReport(-1, "Patching ROCm for your GPU", isIndeterminate: true)); + + await WindowsElevated.MoveFiles(listOfMoves.ToArray()); + } + + private string? GetDownloadUrlFromGpuName(string name) + { + // gfx1201 + if (name.Contains("9060") || name.Contains("9070")) + { + return "https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1201.for.hip.skd.6.2.4-no-optimized.7z"; + } + + // gfx1150 + if ( + name.Contains("8050S") + || name.Contains("8060S") + || name.Contains("880M") + || name.Contains("890M") + ) + { + return "https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1150.for.hip.skd.6.2.4.7z"; + } + + // gfx1103 + if (name.Contains("740M") || name.Contains("760M") || name.Contains("780M") || name.Contains("Z1")) + { + return "https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1103.AMD.780M.phoenix.V5.0.for.hip.sdk.6.2.4.7z"; + } + + // gfx1034, gfx1035, gfx1036 + if ( + name.Contains("6300") + || name.Contains("6400") + || name.Contains("6450") + || name.Contains("6500") + || name.Contains("6550") + || name.Contains("660M") + || name.Contains("680M") + || name.Contains("Graphics 128SP") + ) + { + return "https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1034-gfx1035-gfx1036.for.hip.sdk.6.2.4.7z"; + } + + // gfx1032 + if ( + name.Contains("6700S") + || name.Contains("6800S") + || name.Contains("6600") + || name.Contains("6650") + ) + { + return "https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1032.for.hip.sdk.6.2.4.navi21.logic.7z"; + } + + // gfx1031 + if (name.Contains("6700") || name.Contains("6750") || name.Contains("6800") || name.Contains("6850")) + { + return "https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1031.for.hip.sdk.6.2.4.littlewu.s.logic.7z"; + } + + // gfx1010/1012 + if (name.Contains("5700") || name.Contains("5600") || name.Contains("5300") || name.Contains("5500")) + { + return "https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1010-xnack-gfx1011-xnack-gfx1012-xnack-.for.hip.sdk.6.2.4.7z"; + } + + return null; + } } diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs index 7de0fc0e..3100835c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs @@ -142,6 +142,29 @@ private async Task Install() return; } + if (SelectedPackage.InstallRequiresAdmin) + { + var reason = $""" + # **{SelectedPackage.DisplayName}** may require administrator privileges during the installation. If necessary, you will be prompted to allow the installer to run with elevated privileges. + + ## The reason for this requirement is: + {SelectedPackage.AdminRequiredReason} + + ## Would you like to proceed? + """; + var dialog = DialogHelper.CreateMarkdownDialog(reason, string.Empty); + dialog.PrimaryButtonText = Resources.Action_Yes; + dialog.CloseButtonText = Resources.Action_Cancel; + dialog.IsPrimaryButtonEnabled = true; + dialog.DefaultButton = ContentDialogButton.Primary; + + var result = await dialog.ShowAsync(); + if (result != ContentDialogResult.Primary) + { + return; + } + } + if (SelectedPackage is StableSwarm) { var comfy = settingsManager.Settings.InstalledPackages.FirstOrDefault( diff --git a/StabilityMatrix.Core/Helper/ArchiveHelper.cs b/StabilityMatrix.Core/Helper/ArchiveHelper.cs index 030d8b18..bd05b703 100644 --- a/StabilityMatrix.Core/Helper/ArchiveHelper.cs +++ b/StabilityMatrix.Core/Helper/ArchiveHelper.cs @@ -113,7 +113,7 @@ public static async Task Extract7Z(string archivePath, string extra public static async Task Extract7Z( string archivePath, string extractDirectory, - IProgress progress + IProgress? progress ) { var outputStore = new StringBuilder(); @@ -130,7 +130,7 @@ IProgress progress { var percent = int.Parse(match.Groups[1].Value); var currentFile = match.Groups[2].Value; - progress.Report( + progress?.Report( new ProgressReport( percent / (float)100, "Extracting", @@ -140,7 +140,7 @@ IProgress progress ); } }); - progress.Report(new ProgressReport(-1, isIndeterminate: true, type: ProgressType.Extract)); + progress?.Report(new ProgressReport(-1, isIndeterminate: true, type: ProgressType.Extract)); // Need -bsp1 for progress reports var args = $"x {ProcessRunner.Quote(archivePath)} -o{ProcessRunner.Quote(extractDirectory)} -y -bsp1"; @@ -152,7 +152,7 @@ IProgress progress ProcessException.ThrowIfNonZeroExitCode(process, outputStore); - progress.Report(new ProgressReport(1f, "Finished extracting", type: ProgressType.Extract)); + progress?.Report(new ProgressReport(1f, "Finished extracting", type: ProgressType.Extract)); var output = outputStore.ToString(); diff --git a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs index 54025549..84a359d7 100644 --- a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs +++ b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs @@ -13,6 +13,7 @@ public interface IPrerequisiteHelper bool IsPythonInstalled { get; } bool IsVcBuildToolsInstalled { get; } + bool IsHipSdkInstalled { get; } Task InstallAllIfNecessary(IProgress? progress = null); Task UnpackResourcesIfNecessary(IProgress? progress = null); diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs index e9f2a276..448fcc85 100644 --- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs @@ -54,6 +54,8 @@ public abstract class BasePackage(ISettingsManager settingsManager) public virtual PackageType PackageType => PackageType.SdInference; public virtual bool UsesVenv => true; + public virtual bool InstallRequiresAdmin => false; + public virtual string? AdminRequiredReason => null; /// /// Returns a list of extra commands that can be executed for this package. diff --git a/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs b/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs index 4f395566..6a864f8b 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Text.RegularExpressions; using Injectio.Attributes; +using StabilityMatrix.Core.Exceptions; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; @@ -22,7 +23,7 @@ IPrerequisiteHelper prerequisiteHelper ) : ComfyUI(githubApi, settingsManager, downloadService, prerequisiteHelper) { private const string ZludaPatchDownloadUrl = - "https://github.com/lshqqytiger/ZLUDA/releases/download/rel.c0804ca624963aab420cb418412b1c7fbae3454b/ZLUDA-windows-rocm5-amd64.zip"; + "https://github.com/lshqqytiger/ZLUDA/releases/download/rel.dba64c0966df2c71e82255e942c96e2e1cea3a2d/ZLUDA-windows-rocm6-amd64.zip"; private Process? zludaProcess; public override string Name => "ComfyUI-Zluda"; @@ -32,7 +33,7 @@ IPrerequisiteHelper prerequisiteHelper public override string Blurb => "Windows-only version of ComfyUI which uses ZLUDA to get better performance with AMD GPUs."; public override string Disclaimer => - "Prerequisite install may require admin privileges and a reboot." + "Prerequisite install may require admin privileges and a reboot. " + "AMD GPUs under the RX 6800 may require additional manual setup."; public override string LaunchCommand => Path.Combine("zluda", "zluda.exe"); public override IEnumerable AvailableTorchIndices => [TorchIndex.Zluda]; @@ -44,6 +45,10 @@ IPrerequisiteHelper prerequisiteHelper public override IEnumerable Prerequisites => base.Prerequisites.Concat([PackagePrerequisite.HipSdk]); + public override bool InstallRequiresAdmin => true; + public override string AdminRequiredReason => + "HIP SDK installation and (if applicable) ROCmLibs patching requires admin privileges for accessing the HIP SDK files in the Program Files directory."; + public override async Task InstallPackage( string installLocation, InstalledPackage installedPackage, @@ -53,6 +58,12 @@ public override async Task InstallPackage( CancellationToken cancellationToken = default ) { + if (!PrerequisiteHelper.IsHipSdkInstalled) // for updates + { + progress?.Report(new ProgressReport(-1, "Installing HIP SDK 6.2", isIndeterminate: true)); + await PrerequisiteHelper.InstallPackageRequirements(this, progress).ConfigureAwait(false); + } + progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true)); await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false); await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); @@ -142,20 +153,28 @@ public override async Task RunPackage( CancellationToken cancellationToken = default ) { + if (!PrerequisiteHelper.IsHipSdkInstalled) + { + throw new MissingPrerequisiteException( + "HIP SDK", + "Your package has not yet been upgraded to use HIP SDK 6.2. To continue, please update this package or select \"Change Version\" from the 3-dots menu to have it upgraded automatically for you" + ); + } + await SetupVenv(installLocation).ConfigureAwait(false); var portableGitBin = new DirectoryPath(PrerequisiteHelper.GitBinPath); var hipPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "AMD", "ROCm", - "5.7" + "6.2" ); var hipBinPath = Path.Combine(hipPath, "bin"); var envVars = new Dictionary { ["ZLUDA_COMGR_LOG_LEVEL"] = "1", ["HIP_PATH"] = hipPath, - ["HIP_PATH_57"] = hipPath, + ["HIP_PATH_62"] = hipPath, ["GIT"] = portableGitBin.JoinFile("git.exe") }; envVars.Update(settingsManager.Settings.EnvironmentVariables); diff --git a/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs b/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs index 2c10f9d9..17205af1 100644 --- a/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs +++ b/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using System.Text.RegularExpressions; using Injectio.Attributes; +using StabilityMatrix.Core.Exceptions; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; @@ -30,7 +31,7 @@ IPrerequisiteHelper prerequisiteHelper "https://github.com/lshqqytiger/stable-diffusion-webui-amdgpu-forge/blob/main/LICENSE.txt"; public override string Disclaimer => - "Prerequisite install may require admin privileges and a reboot." + "Prerequisite install may require admin privileges and a reboot. " + "AMD GPUs under the RX 6800 may require additional manual setup."; public override IEnumerable AvailableTorchIndices => [TorchIndex.Zluda]; @@ -57,6 +58,12 @@ IPrerequisiteHelper prerequisiteHelper ) .ToList(); + public override bool InstallRequiresAdmin => true; + + public override string AdminRequiredReason => + "HIP SDK installation and (if applicable) ROCmLibs patching requires admin " + + "privileges for accessing the HIP SDK files in the Program Files directory."; + public override async Task InstallPackage( string installLocation, InstalledPackage installedPackage, @@ -66,6 +73,12 @@ public override async Task InstallPackage( CancellationToken cancellationToken = default ) { + if (!PrerequisiteHelper.IsHipSdkInstalled) // for updates + { + progress?.Report(new ProgressReport(-1, "Installing HIP SDK 6.2", isIndeterminate: true)); + await PrerequisiteHelper.InstallPackageRequirements(this, progress).ConfigureAwait(false); + } + progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true)); await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false); await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); @@ -80,20 +93,28 @@ public override async Task RunPackage( CancellationToken cancellationToken = default ) { + if (!PrerequisiteHelper.IsHipSdkInstalled) + { + throw new MissingPrerequisiteException( + "HIP SDK", + "Your package has not yet been upgraded to use HIP SDK 6.2. To continue, please update this package or select \"Change Version\" from the 3-dots menu to have it upgraded automatically for you" + ); + } + await SetupVenv(installLocation).ConfigureAwait(false); var portableGitBin = new DirectoryPath(PrerequisiteHelper.GitBinPath); var hipPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "AMD", "ROCm", - "5.7" + "6.2" ); var hipBinPath = Path.Combine(hipPath, "bin"); var envVars = new Dictionary { ["ZLUDA_COMGR_LOG_LEVEL"] = "1", ["HIP_PATH"] = hipPath, - ["HIP_PATH_57"] = hipPath, + ["HIP_PATH_62"] = hipPath, ["GIT"] = portableGitBin.JoinFile("git.exe") }; envVars.Update(settingsManager.Settings.EnvironmentVariables); From 8925982fabff12afaf5556dd3de8276486fcd63d Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 31 Mar 2025 21:51:43 -0700 Subject: [PATCH 188/297] shoutout chagenlog shoutout chagenlog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75acd90b..98e32df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Fixed ComfyUI-Zluda not being recognized as an option for Inference or SwarmUI (for real this time) - Fixed missing base model options in the Metadata Editor - Fixed large white boxes appearing when tooltips are visible on macOS/Linux +### Supporters +#### Visionaries +- A special shout-out to our fantastic Visionary-tier Patreon supporters: Waterclouds, Corey T, and our newest Visionary, bluepopsicle! Your continued generosity powers the future of Stability Matrix—thank you so much! ## v2.13.4 ### Added From 95a6ed0a1b5237de220533cf28965c02c199810e Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 01:04:02 -0400 Subject: [PATCH 189/297] Add help guide for weight adjust --- .../ViewModels/Inference/PromptCardViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs index 79565306..aea2e036 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs @@ -254,6 +254,8 @@ private async Task ShowHelpDialog() { var md = $$""" ## {{Resources.Label_Emphasis}} + You can also use (`Ctrl+Up`/`Ctrl+Down`) in the editor to adjust the + weight emphasis of the token under the caret or the currently selected text. ```prompt (keyword) (keyword:1.0) From b70c28186a61a23a864a9ce412b0d748fc680cb0 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 01:08:03 -0400 Subject: [PATCH 190/297] Update CHANGELOG.md --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3fa1d6..4cd03908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,12 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ## v2.14.0-pre.1 ### Added -- Added new Package Command (in the 3-dots menu) for installing Triton & SageAttention in ComfyUI +- Added new Package Command (in the 3-dots menu) for installing Triton & SageAttention in ComfyUI +- Added Abstract Syntax Tree (AST) parsing for Inference prompts. This provides a more robust internal understanding of prompt structure, paving the way for future enhancements. +- Added hotkey (`Ctrl+Up`/`Ctrl+Down`) in Inference prompt editors to adjust the weight emphasis of the token under the caret or the currently selected text. + - This automatically wraps the token/selection in parentheses `()` if it's not already weighted. + - It modifies existing weights within parentheses or adds weights if none exist (e.g. `(word:1.1)`). + - Handles selection spanning multiple tokens intelligently. ### Changed - Changed the names of some of the shared model folders to better reflect their contents - Improved window state handling From 45a3dfcc5aaad1400cab0eb1968ac57bae8829a2 Mon Sep 17 00:00:00 2001 From: jt Date: Mon, 31 Mar 2025 22:39:34 -0700 Subject: [PATCH 191/297] Fix config model sharing with new shared folder types & added more packages to DiscordRichPresence thing --- .../Services/DiscordRichPresenceService.cs | 92 ++++++----- .../Dialogs/SelectModelVersionViewModel.cs | 2 +- .../Models/Packages/ComfyUI.cs | 152 ++++++++++++------ StabilityMatrix.Core/Models/Packages/Sdfx.cs | 54 ++++--- .../Models/Packages/VladAutomatic.cs | 85 ++++++++-- 5 files changed, 261 insertions(+), 124 deletions(-) diff --git a/StabilityMatrix.Avalonia/Services/DiscordRichPresenceService.cs b/StabilityMatrix.Avalonia/Services/DiscordRichPresenceService.cs index 5efd5e8b..21d94154 100644 --- a/StabilityMatrix.Avalonia/Services/DiscordRichPresenceService.cs +++ b/StabilityMatrix.Avalonia/Services/DiscordRichPresenceService.cs @@ -12,47 +12,45 @@ namespace StabilityMatrix.Avalonia.Services; public class DiscordRichPresenceService : IDiscordRichPresenceService { private const string ApplicationId = "1134669805237059615"; - + private readonly ILogger logger; private readonly ISettingsManager settingsManager; private readonly DiscordRpcClient client; private readonly string appDetails; private bool isDisposed; - private RichPresence DefaultPresence => new() - { - Details = appDetails, - Assets = new DiscordRPC.Assets + private RichPresence DefaultPresence => + new() { - LargeImageKey = "stabilitymatrix-logo-1", - LargeImageText = $"Stability Matrix {appDetails}", - }, - Buttons = new[] - { - new Button + Details = appDetails, + Assets = new DiscordRPC.Assets + { + LargeImageKey = "stabilitymatrix-logo-1", + LargeImageText = $"Stability Matrix {appDetails}", + }, + Buttons = new[] { - Label = "GitHub", - Url = "https://github.com/LykosAI/StabilityMatrix", + new Button { Label = "GitHub", Url = "https://github.com/LykosAI/StabilityMatrix", } } - } - }; - + }; + public DiscordRichPresenceService( ILogger logger, - ISettingsManager settingsManager) + ISettingsManager settingsManager + ) { this.logger = logger; this.settingsManager = settingsManager; - + appDetails = $"v{Compat.AppVersion.WithoutMetadata()}"; - + client = new DiscordRpcClient(ApplicationId); client.Logger = new NullLogger(); client.OnReady += OnReady; client.OnError += OnError; client.OnClose += OnClose; client.OnPresenceUpdate += OnPresenceUpdate; - + settingsManager.SettingsPropertyChanged += (sender, args) => { if (args.PropertyName == nameof(settingsManager.Settings.IsDiscordRichPresenceEnabled)) @@ -60,34 +58,35 @@ public DiscordRichPresenceService( UpdateState(); } }; - + EventManager.Instance.RunningPackageStatusChanged += OnRunningPackageStatusChanged; } - + private void OnReady(object sender, ReadyMessage args) { logger.LogInformation("Received Ready from user {User}", args.User.Username); } - + private void OnError(object sender, ErrorMessage args) { logger.LogWarning("Received Error: {Message}", args.Message); } - + private void OnClose(object sender, CloseMessage args) { logger.LogInformation("Received Close: {Reason}", args.Reason); } - + private void OnPresenceUpdate(object sender, PresenceMessage args) { logger.LogDebug("Received Update: {Presence}", args.Presence.ToString()); } - + private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusChangedEventArgs args) { - if (!client.IsInitialized || !settingsManager.Settings.IsDiscordRichPresenceEnabled) return; - + if (!client.IsInitialized || !settingsManager.Settings.IsDiscordRichPresenceEnabled) + return; + if (args.CurrentPackagePair is null) { client.SetPresence(DefaultPresence); @@ -98,16 +97,25 @@ private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusC var packageTitle = args.CurrentPackagePair.BasePackage switch { - A3WebUI => "Automatic1111 Web UI", - VladAutomatic => "SD.Next Web UI", - ComfyUI => "ComfyUI", - VoltaML => "VoltaML", + FluxGym => "FluxGym", + Fooocus => "Fooocus", + Reforge => "SD WebUI reForge", + Sdfx => "SDFX", + StableSwarm => "SwarmUI", + SDWebForge or ForgeAmdGpu => "SD WebUI Forge", + KohyaSs => "KohyaSS", + OneTrainer => "OneTrainer", + Cogstudio => "Cogstudio", + ComfyUI or ComfyZluda => "ComfyUI", InvokeAI => "InvokeAI", + VoltaML => "VoltaML", + VladAutomatic => "SD.Next Web UI", + A3WebUI => "Automatic1111 Web UI", _ => "Stable Diffusion" }; presence.State = $"Running {packageTitle}"; - + presence.Assets.SmallImageText = presence.State; presence.Assets.SmallImageKey = args.CurrentPackagePair.BasePackage switch { @@ -117,15 +125,17 @@ private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusC _ => "ic_fluent_box_512_filled" }; - presence.WithTimestamps(new Timestamps - { - StartUnixMilliseconds = (ulong?) DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - }); - + presence.WithTimestamps( + new Timestamps + { + StartUnixMilliseconds = (ulong?)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + } + ); + client.SetPresence(presence); } } - + public void UpdateState() { // Set initial rich presence @@ -152,7 +162,7 @@ public void UpdateState() } } } - + public void Dispose() { if (!isDisposed) @@ -168,7 +178,7 @@ public void Dispose() isDisposed = true; GC.SuppressFinalize(this); } - + ~DiscordRichPresenceService() { Dispose(); diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs index 44bfa0c5..72fdb003 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs @@ -333,7 +333,7 @@ var directory in downloadDirectory.EnumerateDirectories( } } - if (downloadDirectory.ToString().EndsWith("Unet")) + if (downloadDirectory.ToString().EndsWith(SharedFolderType.DiffusionModels.GetStringValue())) { // also add StableDiffusion in case we have an AIO version var stableDiffusionDirectory = rootModelsDirectory.JoinDir( diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 74c49162..fcca571b 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -408,38 +408,77 @@ private async Task SetupModelFoldersConfig(DirectoryPath installDirectory) if (stabilityMatrixNode.Value is not YamlMappingNode nodeValue) return; - nodeValue.Children["checkpoints"] = Path.Combine(modelsDir, "StableDiffusion"); - nodeValue.Children["vae"] = Path.Combine(modelsDir, "VAE"); + nodeValue.Children["checkpoints"] = Path.Combine( + modelsDir, + SharedFolderType.StableDiffusion.GetStringValue() + ); + nodeValue.Children["vae"] = Path.Combine(modelsDir, SharedFolderType.VAE.GetStringValue()); nodeValue.Children["loras"] = - $"{Path.Combine(modelsDir, "Lora")}\n" + $"{Path.Combine(modelsDir, "LyCORIS")}"; + $"{Path.Combine(modelsDir, SharedFolderType.Lora.GetStringValue())}\n" + + $"{Path.Combine(modelsDir, SharedFolderType.LyCORIS.GetStringValue())}"; nodeValue.Children["upscale_models"] = - $"{Path.Combine(modelsDir, "ESRGAN")}\n" - + $"{Path.Combine(modelsDir, "RealESRGAN")}\n" - + $"{Path.Combine(modelsDir, "SwinIR")}"; - nodeValue.Children["embeddings"] = Path.Combine(modelsDir, "TextualInversion"); - nodeValue.Children["hypernetworks"] = Path.Combine(modelsDir, "Hypernetwork"); + $"{Path.Combine(modelsDir, SharedFolderType.ESRGAN.GetStringValue())}\n" + + $"{Path.Combine(modelsDir, SharedFolderType.RealESRGAN.GetStringValue())}\n" + + $"{Path.Combine(modelsDir, SharedFolderType.SwinIR.GetStringValue())}"; + nodeValue.Children["embeddings"] = Path.Combine( + modelsDir, + SharedFolderType.Embeddings.GetStringValue() + ); + nodeValue.Children["hypernetworks"] = Path.Combine( + modelsDir, + SharedFolderType.Hypernetwork.GetStringValue() + ); nodeValue.Children["controlnet"] = string.Join( '\n', - Path.Combine(modelsDir, "ControlNet"), - Path.Combine(modelsDir, "T2IAdapter") + Path.Combine(modelsDir, SharedFolderType.ControlNet.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.T2IAdapter.GetStringValue()) + ); + nodeValue.Children["clip"] = Path.Combine( + modelsDir, + SharedFolderType.TextEncoders.GetStringValue() + ); + nodeValue.Children["clip_vision"] = Path.Combine( + modelsDir, + SharedFolderType.ClipVision.GetStringValue() + ); + nodeValue.Children["diffusers"] = Path.Combine( + modelsDir, + SharedFolderType.Diffusers.GetStringValue() + ); + nodeValue.Children["gligen"] = Path.Combine(modelsDir, SharedFolderType.GLIGEN.GetStringValue()); + nodeValue.Children["vae_approx"] = Path.Combine( + modelsDir, + SharedFolderType.ApproxVAE.GetStringValue() ); - nodeValue.Children["clip"] = Path.Combine(modelsDir, "CLIP"); - nodeValue.Children["clip_vision"] = Path.Combine(modelsDir, "InvokeClipVision"); - nodeValue.Children["diffusers"] = Path.Combine(modelsDir, "Diffusers"); - nodeValue.Children["gligen"] = Path.Combine(modelsDir, "GLIGEN"); - nodeValue.Children["vae_approx"] = Path.Combine(modelsDir, "ApproxVAE"); nodeValue.Children["ipadapter"] = string.Join( '\n', - Path.Combine(modelsDir, "IpAdapter"), - Path.Combine(modelsDir, "InvokeIpAdapters15"), - Path.Combine(modelsDir, "InvokeIpAdaptersXl") + Path.Combine(modelsDir, SharedFolderType.IpAdapter.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.IpAdapters15.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.IpAdaptersXl.GetStringValue()) + ); + nodeValue.Children["prompt_expansion"] = Path.Combine( + modelsDir, + SharedFolderType.PromptExpansion.GetStringValue() + ); + nodeValue.Children["ultralytics"] = Path.Combine( + modelsDir, + SharedFolderType.Ultralytics.GetStringValue() + ); + nodeValue.Children["ultralytics_bbox"] = Path.Combine( + modelsDir, + SharedFolderType.Ultralytics.GetStringValue(), + "bbox" + ); + nodeValue.Children["ultralytics_segm"] = Path.Combine( + modelsDir, + SharedFolderType.Ultralytics.GetStringValue(), + "segm" + ); + nodeValue.Children["sams"] = Path.Combine(modelsDir, SharedFolderType.Sams.GetStringValue()); + nodeValue.Children["diffusion_models"] = Path.Combine( + modelsDir, + SharedFolderType.DiffusionModels.GetStringValue() ); - nodeValue.Children["prompt_expansion"] = Path.Combine(modelsDir, "PromptExpansion"); - nodeValue.Children["ultralytics"] = Path.Combine(modelsDir, "Ultralytics"); - nodeValue.Children["ultralytics_bbox"] = Path.Combine(modelsDir, "Ultralytics", "bbox"); - nodeValue.Children["ultralytics_segm"] = Path.Combine(modelsDir, "Ultralytics", "segm"); - nodeValue.Children["sams"] = Path.Combine(modelsDir, "Sams"); - nodeValue.Children["diffusion_models"] = Path.Combine(modelsDir, "Unet"); } else { @@ -447,43 +486,64 @@ private async Task SetupModelFoldersConfig(DirectoryPath installDirectory) new YamlScalarNode("stability_matrix"), new YamlMappingNode { - { "checkpoints", Path.Combine(modelsDir, "StableDiffusion") }, - { "vae", Path.Combine(modelsDir, "VAE") }, - { "loras", $"{Path.Combine(modelsDir, "Lora")}\n{Path.Combine(modelsDir, "LyCORIS")}" }, + { + "checkpoints", + Path.Combine(modelsDir, SharedFolderType.StableDiffusion.GetStringValue()) + }, + { "vae", Path.Combine(modelsDir, SharedFolderType.VAE.GetStringValue()) }, + { + "loras", + $"{Path.Combine(modelsDir, SharedFolderType.Lora.GetStringValue())}\n{Path.Combine(modelsDir, SharedFolderType.LyCORIS.GetStringValue())}" + }, { "upscale_models", - $"{Path.Combine(modelsDir, "ESRGAN")}\n{Path.Combine(modelsDir, "RealESRGAN")}\n{Path.Combine(modelsDir, "SwinIR")}" + $"{Path.Combine(modelsDir, SharedFolderType.ESRGAN.GetStringValue())}\n{Path.Combine(modelsDir, SharedFolderType.RealESRGAN.GetStringValue())}\n{Path.Combine(modelsDir, SharedFolderType.SwinIR.GetStringValue())}" + }, + { "embeddings", Path.Combine(modelsDir, SharedFolderType.Embeddings.GetStringValue()) }, + { + "hypernetworks", + Path.Combine(modelsDir, SharedFolderType.Hypernetwork.GetStringValue()) }, - { "embeddings", Path.Combine(modelsDir, "TextualInversion") }, - { "hypernetworks", Path.Combine(modelsDir, "Hypernetwork") }, { "controlnet", string.Join( '\n', - Path.Combine(modelsDir, "ControlNet"), - Path.Combine(modelsDir, "T2IAdapter") + Path.Combine(modelsDir, SharedFolderType.ControlNet.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.T2IAdapter.GetStringValue()) ) }, - { "clip", Path.Combine(modelsDir, "CLIP") }, - { "clip_vision", Path.Combine(modelsDir, "InvokeClipVision") }, - { "diffusers", Path.Combine(modelsDir, "Diffusers") }, - { "gligen", Path.Combine(modelsDir, "GLIGEN") }, - { "vae_approx", Path.Combine(modelsDir, "ApproxVAE") }, + { "clip", Path.Combine(modelsDir, SharedFolderType.TextEncoders.GetStringValue()) }, + { "clip_vision", Path.Combine(modelsDir, SharedFolderType.ClipVision.GetStringValue()) }, + { "diffusers", Path.Combine(modelsDir, SharedFolderType.Diffusers.GetStringValue()) }, + { "gligen", Path.Combine(modelsDir, SharedFolderType.GLIGEN.GetStringValue()) }, + { "vae_approx", Path.Combine(modelsDir, SharedFolderType.ApproxVAE.GetStringValue()) }, { "ipadapter", string.Join( '\n', - Path.Combine(modelsDir, "IpAdapter"), - Path.Combine(modelsDir, "InvokeIpAdapters15"), - Path.Combine(modelsDir, "InvokeIpAdaptersXl") + Path.Combine(modelsDir, SharedFolderType.IpAdapter.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.IpAdapters15.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.IpAdaptersXl.GetStringValue()) ) }, - { "prompt_expansion", Path.Combine(modelsDir, "PromptExpansion") }, - { "ultralytics", Path.Combine(modelsDir, "Ultralytics") }, - { "ultralytics_bbox", Path.Combine(modelsDir, "Ultralytics", "bbox") }, - { "ultralytics_segm", Path.Combine(modelsDir, "Ultralytics", "segm") }, - { "sams", Path.Combine(modelsDir, "Sams") }, - { "diffusion_models", Path.Combine(modelsDir, "Unet") } + { + "prompt_expansion", + Path.Combine(modelsDir, SharedFolderType.PromptExpansion.GetStringValue()) + }, + { "ultralytics", Path.Combine(modelsDir, SharedFolderType.Ultralytics.GetStringValue()) }, + { + "ultralytics_bbox", + Path.Combine(modelsDir, SharedFolderType.Ultralytics.GetStringValue(), "bbox") + }, + { + "ultralytics_segm", + Path.Combine(modelsDir, SharedFolderType.Ultralytics.GetStringValue(), "segm") + }, + { "sams", Path.Combine(modelsDir, SharedFolderType.Sams.GetStringValue()) }, + { + "diffusion_models", + Path.Combine(modelsDir, SharedFolderType.DiffusionModels.GetStringValue()) + } } ); } diff --git a/StabilityMatrix.Core/Models/Packages/Sdfx.cs b/StabilityMatrix.Core/Models/Packages/Sdfx.cs index c1d7d83b..2cd7909e 100644 --- a/StabilityMatrix.Core/Models/Packages/Sdfx.cs +++ b/StabilityMatrix.Core/Models/Packages/Sdfx.cs @@ -216,32 +216,48 @@ private async Task SetupModelFoldersConfig(DirectoryPath installDirectory) var models = config.GetOrAddNonNullJsonObject(["paths", "models"]); - models["checkpoints"] = new JsonArray(Path.Combine(modelsDir, "StableDiffusion")); - models["vae"] = new JsonArray(Path.Combine(modelsDir, "VAE")); + models["checkpoints"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.StableDiffusion.GetStringValue()) + ); + models["vae"] = new JsonArray(Path.Combine(modelsDir, SharedFolderType.VAE.GetStringValue())); models["loras"] = new JsonArray( - Path.Combine(modelsDir, "Lora"), - Path.Combine(modelsDir, "LyCORIS") + Path.Combine(modelsDir, SharedFolderType.Lora.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.LyCORIS.GetStringValue()) ); models["upscale_models"] = new JsonArray( - Path.Combine(modelsDir, "ESRGAN"), - Path.Combine(modelsDir, "RealESRGAN"), - Path.Combine(modelsDir, "SwinIR") + Path.Combine(modelsDir, SharedFolderType.ESRGAN.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.RealESRGAN.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.SwinIR.GetStringValue()) + ); + models["embeddings"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.Embeddings.GetStringValue()) + ); + models["hypernetworks"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.Hypernetwork.GetStringValue()) ); - models["embeddings"] = new JsonArray(Path.Combine(modelsDir, "TextualInversion")); - models["hypernetworks"] = new JsonArray(Path.Combine(modelsDir, "Hypernetwork")); models["controlnet"] = new JsonArray( - Path.Combine(modelsDir, "ControlNet"), - Path.Combine(modelsDir, "T2IAdapter") + Path.Combine(modelsDir, SharedFolderType.ControlNet.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.T2IAdapter.GetStringValue()) + ); + models["clip"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.TextEncoders.GetStringValue()) + ); + models["clip_vision"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.ClipVision.GetStringValue()) + ); + models["diffusers"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.Diffusers.GetStringValue()) + ); + models["gligen"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.GLIGEN.GetStringValue()) + ); + models["vae_approx"] = new JsonArray( + Path.Combine(modelsDir, SharedFolderType.ApproxVAE.GetStringValue()) ); - models["clip"] = new JsonArray(Path.Combine(modelsDir, "CLIP")); - models["clip_vision"] = new JsonArray(Path.Combine(modelsDir, "InvokeClipVision")); - models["diffusers"] = new JsonArray(Path.Combine(modelsDir, "Diffusers")); - models["gligen"] = new JsonArray(Path.Combine(modelsDir, "GLIGEN")); - models["vae_approx"] = new JsonArray(Path.Combine(modelsDir, "ApproxVAE")); models["ipadapter"] = new JsonArray( - Path.Combine(modelsDir, "IpAdapter"), - Path.Combine(modelsDir, "InvokeIpAdapters15"), - Path.Combine(modelsDir, "InvokeIpAdaptersXl") + Path.Combine(modelsDir, SharedFolderType.IpAdapter.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.IpAdapters15.GetStringValue()), + Path.Combine(modelsDir, SharedFolderType.IpAdaptersXl.GetStringValue()) ); await File.WriteAllTextAsync(configPath, config.ToString()).ConfigureAwait(false); diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index 6307a763..f0e8e376 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -422,23 +422,74 @@ SharedFolderMethod sharedFolderMethod configRoot = new JsonObject(); } - configRoot["ckpt_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "StableDiffusion"); - configRoot["diffusers_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "Diffusers"); - configRoot["vae_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "VAE"); - configRoot["lora_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "Lora"); - configRoot["lyco_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "LyCORIS"); - configRoot["embeddings_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "TextualInversion"); - configRoot["hypernetwork_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "Hypernetwork"); - configRoot["codeformer_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "Codeformer"); - configRoot["gfpgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "GFPGAN"); - configRoot["bsrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "BSRGAN"); - configRoot["esrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ESRGAN"); - configRoot["realesrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "RealESRGAN"); - configRoot["scunet_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ScuNET"); - configRoot["swinir_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "SwinIR"); - configRoot["ldsr_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "LDSR"); - configRoot["clip_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "CLIP"); - configRoot["control_net_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ControlNet"); + configRoot["ckpt_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.StableDiffusion.GetStringValue() + ); + configRoot["diffusers_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.Diffusers.GetStringValue() + ); + configRoot["vae_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.VAE.GetStringValue() + ); + configRoot["lora_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.Lora.GetStringValue() + ); + configRoot["lyco_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.LyCORIS.GetStringValue() + ); + configRoot["embeddings_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.Embeddings.GetStringValue() + ); + configRoot["hypernetwork_dir"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.Hypernetwork.GetStringValue() + ); + configRoot["codeformer_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.Codeformer.GetStringValue() + ); + configRoot["gfpgan_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.GFPGAN.GetStringValue() + ); + configRoot["bsrgan_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.BSRGAN.GetStringValue() + ); + configRoot["esrgan_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.ESRGAN.GetStringValue() + ); + configRoot["realesrgan_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.RealESRGAN.GetStringValue() + ); + configRoot["scunet_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.ScuNET.GetStringValue() + ); + configRoot["swinir_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.SwinIR.GetStringValue() + ); + configRoot["ldsr_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.LDSR.GetStringValue() + ); + configRoot["clip_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.TextEncoders.GetStringValue() + ); + configRoot["control_net_models_path"] = Path.Combine( + SettingsManager.ModelsDirectory, + SharedFolderType.ControlNet.GetStringValue() + ); var configJsonStr = JsonSerializer.Serialize( configRoot, From c129fe0769a081ba90c194f29fe621d41102bd1b Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 03:37:54 -0400 Subject: [PATCH 192/297] Add enhanced config sharing framework --- .../Helper/SharedFoldersConfigHelper.cs | 195 +++++++------- .../Models/Packages/Config/ConfigFileType.cs | 8 + .../Packages/Config/ConfigSharingOptions.cs | 18 ++ .../Config/FdsConfigSharingStrategy.cs | 122 +++++++++ .../Packages/Config/IConfigSharingStrategy.cs | 15 ++ .../Config/JsonConfigSharingStrategy.cs | 134 +++++++++ .../Config/YamlConfigSharingStrategy.cs | 254 ++++++++++++++++++ .../Models/Packages/SharedFolderLayout.cs | 5 + .../Models/Packages/SharedFolderLayoutRule.cs | 9 +- 9 files changed, 664 insertions(+), 96 deletions(-) create mode 100644 StabilityMatrix.Core/Models/Packages/Config/ConfigFileType.cs create mode 100644 StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs create mode 100644 StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs create mode 100644 StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs create mode 100644 StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs create mode 100644 StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs diff --git a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs index 3d9f86b5..6770fde7 100644 --- a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs +++ b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs @@ -1,145 +1,150 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using StabilityMatrix.Core.Extensions; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Models.Packages; +using StabilityMatrix.Core.Models.Packages.Config; namespace StabilityMatrix.Core.Helper; public static class SharedFoldersConfigHelper { - /// - /// Updates a JSON object with shared folder layout rules, using the SourceTypes, - /// converted to absolute paths using the sharedModelsDirectory. - /// - public static async Task UpdateJsonConfigFileForSharedAsync( + // Cache strategies to avoid repeated instantiation + private static readonly Dictionary Strategies = + new() + { + { ConfigFileType.Json, new JsonConfigSharingStrategy() }, + { ConfigFileType.Yaml, new YamlConfigSharingStrategy() }, + { ConfigFileType.Fds, new FdsConfigSharingStrategy() } + // Add more strategies here as needed + }; + + public static Task UpdateConfigFileForSharedAsync( SharedFolderLayout layout, string packageRootDirectory, string sharedModelsDirectory, - SharedFoldersConfigOptions? options = null + CancellationToken cancellationToken = default ) { - var configPath = Path.Combine( + return UpdateConfigFileForSharedAsync( + layout, packageRootDirectory, - layout.RelativeConfigPath ?? throw new InvalidOperationException("RelativeConfigPath is null") + sharedModelsDirectory, + layout.ConfigFileType ?? throw new InvalidOperationException("ConfigFileType is null"), + layout.ConfigSharingOptions, + cancellationToken ); - - await using var stream = File.Open(configPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); - - await UpdateJsonConfigFileAsync( - layout, - stream, - rule => - rule.SourceTypes.Select( - type => Path.Combine(sharedModelsDirectory, type.GetStringValue()) - ), - options - ) - .ConfigureAwait(false); } /// - /// Updates a JSON object with shared folder layout rules, using the TargetRelativePaths, - /// converted to absolute paths using the packageRootDirectory. + /// Updates a config file with shared folder layout rules, using the SourceTypes, + /// converted to absolute paths using the sharedModelsDirectory. /// - public static async Task UpdateJsonConfigFileForDefaultAsync( + public static async Task UpdateConfigFileForSharedAsync( SharedFolderLayout layout, string packageRootDirectory, - SharedFoldersConfigOptions? options = null + string sharedModelsDirectory, + ConfigFileType fileType, // Specify the file type + ConfigSharingOptions? options = null, + CancellationToken cancellationToken = default ) { + options ??= ConfigSharingOptions.Default; var configPath = Path.Combine( packageRootDirectory, layout.RelativeConfigPath ?? throw new InvalidOperationException("RelativeConfigPath is null") ); + Directory.CreateDirectory(Path.GetDirectoryName(configPath)!); // Ensure directory exists + + // Using FileStream ensures we handle file locking and async correctly + await using var stream = new FileStream( + configPath, + FileMode.OpenOrCreate, + FileAccess.ReadWrite, + FileShare.None + ); - await using var stream = File.Open(configPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); + if (!Strategies.TryGetValue(fileType, out var strategy)) + { + throw new NotSupportedException($"Configuration file type '{fileType}' is not supported."); + } - await UpdateJsonConfigFileAsync( - layout, + await strategy + .UpdateAndWriteAsync( stream, + layout, rule => - rule.TargetRelativePaths.Select(NormalizePathSlashes) - .Select(path => Path.Combine(packageRootDirectory, path)), - options + rule.SourceTypes.Select(type => type.GetStringValue()) // Get the enum string value (e.g., "StableDiffusion") + .Where(folderName => !string.IsNullOrEmpty(folderName)) // Filter out potentially empty mappings + .Select(folderName => Path.Combine(sharedModelsDirectory, folderName)), // Combine with base models dir + options, + cancellationToken ) .ConfigureAwait(false); } - private static async Task UpdateJsonConfigFileAsync( + public static Task UpdateConfigFileForDefaultAsync( SharedFolderLayout layout, - Stream configStream, - Func> pathsSelector, - SharedFoldersConfigOptions? options = null + string packageRootDirectory, + CancellationToken cancellationToken = default ) { - options ??= SharedFoldersConfigOptions.Default; - - JsonObject jsonNode; - - if (configStream.Length == 0) - { - jsonNode = new JsonObject(); - } - else - { - jsonNode = - await JsonSerializer - .DeserializeAsync(configStream, options.JsonSerializerOptions) - .ConfigureAwait(false) ?? new JsonObject(); - } - - UpdateJsonConfig(layout, jsonNode, pathsSelector, options); - - configStream.Seek(0, SeekOrigin.Begin); - configStream.SetLength(0); - - await JsonSerializer - .SerializeAsync(configStream, jsonNode, options.JsonSerializerOptions) - .ConfigureAwait(false); + return UpdateConfigFileForDefaultAsync( + layout, + packageRootDirectory, + layout.ConfigFileType ?? throw new InvalidOperationException("ConfigFileType is null"), + layout.ConfigSharingOptions, + cancellationToken + ); } - private static void UpdateJsonConfig( + /// + /// Updates a config file with shared folder layout rules, using the TargetRelativePaths, + /// converted to absolute paths using the packageRootDirectory (restores default paths). + /// + public static async Task UpdateConfigFileForDefaultAsync( SharedFolderLayout layout, - JsonObject jsonObject, - Func> pathsSelector, - SharedFoldersConfigOptions? options = null + string packageRootDirectory, + ConfigFileType fileType, // Specify the file type + ConfigSharingOptions? options = null, + CancellationToken cancellationToken = default ) { - options ??= SharedFoldersConfigOptions.Default; - - var rulesByConfigPath = layout.GetRulesByConfigPath(); + options ??= ConfigSharingOptions.Default; + var configPath = Path.Combine( + packageRootDirectory, + layout.RelativeConfigPath ?? throw new InvalidOperationException("RelativeConfigPath is null") + ); + Directory.CreateDirectory(Path.GetDirectoryName(configPath)!); // Ensure directory exists + + // Using FileStream ensures we handle file locking and async correctly + await using var stream = new FileStream( + configPath, + FileMode.OpenOrCreate, + FileAccess.ReadWrite, + FileShare.None + ); - foreach (var (configPath, rule) in rulesByConfigPath) + if (!Strategies.TryGetValue(fileType, out var strategy)) { - // Get paths to write with selector - var paths = pathsSelector(rule).ToArray(); - - // Multiple elements or alwaysWriteArray is true, write as array - if (paths.Length > 1 || options.AlwaysWriteArray) - { - jsonObject[configPath] = new JsonArray( - paths.Select(path => (JsonNode)JsonValue.Create(path)).ToArray() - ); - } - // 1 element and alwaysWriteArray is false, write as string - else if (paths.Length == 1) - { - jsonObject[configPath] = paths[0]; - } - else - { - jsonObject.Remove(configPath); - } + throw new NotSupportedException($"Configuration file type '{fileType}' is not supported."); } + + await strategy + .UpdateAndWriteAsync( + stream, + layout, + rule => + rule.TargetRelativePaths.Select( + path => Path.Combine(packageRootDirectory, NormalizePathSlashes(path)) + ), // Combine relative with package root + options, + cancellationToken + ) + .ConfigureAwait(false); } + // Keep path normalization logic accessible if needed elsewhere, or inline it if only used here. private static string NormalizePathSlashes(string path) { - if (Compat.IsWindows) - { - return path.Replace('/', '\\'); - } - - return path; + // Replace forward slashes with backslashes on Windows, otherwise use forward slashes. + return path.Replace(Compat.IsWindows ? '/' : '\\', Path.DirectorySeparatorChar); } } diff --git a/StabilityMatrix.Core/Models/Packages/Config/ConfigFileType.cs b/StabilityMatrix.Core/Models/Packages/Config/ConfigFileType.cs new file mode 100644 index 00000000..3668dd42 --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Config/ConfigFileType.cs @@ -0,0 +1,8 @@ +namespace StabilityMatrix.Core.Models.Packages.Config; + +public enum ConfigFileType +{ + Json, + Yaml, + Fds // Frenetic Data Syntax +} diff --git a/StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs b/StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs new file mode 100644 index 00000000..ff2df697 --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace StabilityMatrix.Core.Models.Packages.Config; + +// Options might need expansion later if format-specific settings are required +public record ConfigSharingOptions +{ + public static ConfigSharingOptions Default { get; } = new(); + + // For JSON: + public JsonSerializerOptions JsonSerializerOptions { get; set; } = new() { WriteIndented = true }; + + // For JSON/YAML: Write single paths as arrays? + public bool AlwaysWriteArray { get; set; } = false; + + // For YAML/FDS: Key under which to store SM paths (e.g., "stability_matrix") + public string? RootKey { get; set; } +} diff --git a/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs new file mode 100644 index 00000000..44070ab3 --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs @@ -0,0 +1,122 @@ +using FreneticUtilities.FreneticDataSyntax; + +namespace StabilityMatrix.Core.Models.Packages.Config; + +public class FdsConfigSharingStrategy : IConfigSharingStrategy +{ + public async Task UpdateAndWriteAsync( + Stream configStream, + SharedFolderLayout layout, + Func> pathsSelector, + ConfigSharingOptions options, + CancellationToken cancellationToken = default + ) + { + FDSSection rootSection; + var initialPosition = configStream.Position; + var isEmpty = configStream.Length - initialPosition == 0; + + if (!isEmpty) + { + try + { + // FDSUtility reads from the current position + using var reader = new StreamReader(configStream, leaveOpen: true); + var fdsContent = await reader.ReadToEndAsync().ConfigureAwait(false); + rootSection = new FDSSection(fdsContent); + } + catch (Exception ex) // FDSUtility might throw various exceptions on parse errors + { + System.Diagnostics.Debug.WriteLine( + $"Error deserializing FDS config: {ex.Message}. Treating as new." + ); + rootSection = new FDSSection(); + isEmpty = true; + } + } + else + { + rootSection = new FDSSection(); + } + + UpdateFdsConfig(layout, rootSection, pathsSelector, options); + + // Reset stream to original position before writing + configStream.Seek(initialPosition, SeekOrigin.Begin); + // Truncate the stream + configStream.SetLength(initialPosition + 0); + + // Save using a StreamWriter to control encoding and leave stream open + await using (var writer = new StreamWriter(configStream, System.Text.Encoding.UTF8, leaveOpen: true)) + { + await writer + .WriteAsync(rootSection.SaveToString().AsMemory(), cancellationToken) + .ConfigureAwait(false); + await writer.FlushAsync().ConfigureAwait(false); // Ensure content is written + } + await configStream.FlushAsync(cancellationToken).ConfigureAwait(false); // Flush the underlying stream + } + + private static void UpdateFdsConfig( + SharedFolderLayout layout, + FDSSection rootSection, + Func> pathsSelector, + ConfigSharingOptions options + ) + { + var rulesByConfigPath = layout.GetRulesByConfigPath(); + + // SwarmUI typically stores paths under a "Paths" section + var pathsSection = rootSection.GetSection("Paths") ?? new FDSSection(); // Get or create "Paths" section + + // Keep track of keys managed by the layout to remove old ones + var allRuleKeys = rulesByConfigPath.Keys.ToHashSet(); + var currentKeysInPathsSection = pathsSection.GetRootKeys(); // Assuming FDS has a way to list keys + + foreach (var (configPath, rule) in rulesByConfigPath) + { + var paths = pathsSelector(rule).ToArray(); + + // Normalize paths for FDS - likely prefers native OS slashes or forward slashes + var normalizedPaths = paths.Select(p => p.Replace('/', Path.DirectorySeparatorChar)).ToList(); + + if (normalizedPaths.Count > 0) + { + // FDS might store lists separated by newline or another char, or just the first path? + // Assuming SwarmUI expects a single path string per key, potentially the first one. + // If it supports lists (e.g., newline separated), adjust here. + // For now, let's assume it takes the first path if multiple are generated, + // or handles lists internally if the key implies it (needs SwarmUI knowledge). + pathsSection.Set(configPath, normalizedPaths.First()); + + // If FDS supports lists explicitly (e.g., via SetList), use that: + // pathsSection.SetList(configPath, normalizedPaths); + } + else + { + // No paths for this rule, remove the key + pathsSection.Remove(configPath); // Assuming Remove method exists + } + } + + // Remove any keys in the Paths section that are no longer defined by any rule + foreach (var existingKey in currentKeysInPathsSection) + { + if (!allRuleKeys.Contains(existingKey)) + { + pathsSection.Remove(existingKey); + } + } + + // If the Paths section is not empty, add/update it in the root + if (pathsSection.GetRootKeys().Any()) // Check if the section has content + { + rootSection.Set("Paths", pathsSection); + // rootSection.SetSection("Paths", pathsSection); + } + else // Otherwise, remove the empty Paths section from the root + { + rootSection.Remove("Paths"); // Assuming Remove method exists for sections too + } + } +} diff --git a/StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs new file mode 100644 index 00000000..b1d6c01b --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs @@ -0,0 +1,15 @@ +namespace StabilityMatrix.Core.Models.Packages.Config; + +public interface IConfigSharingStrategy +{ + /// + /// Reads the config stream, updates paths based on the layout and selector, and writes back to the stream. + /// + Task UpdateAndWriteAsync( + Stream configStream, + SharedFolderLayout layout, + Func> pathsSelector, + ConfigSharingOptions options, + CancellationToken cancellationToken = default + ); +} diff --git a/StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs new file mode 100644 index 00000000..94ea37d0 --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs @@ -0,0 +1,134 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace StabilityMatrix.Core.Models.Packages.Config; + +public class JsonConfigSharingStrategy : IConfigSharingStrategy +{ + public async Task UpdateAndWriteAsync( + Stream configStream, + SharedFolderLayout layout, + Func> pathsSelector, + ConfigSharingOptions options, + CancellationToken cancellationToken = default + ) + { + JsonObject jsonNode; + var initialPosition = configStream.Position; + var isEmpty = configStream.Length - initialPosition == 0; + + if (isEmpty) + { + jsonNode = new JsonObject(); + } + else + { + try + { + // Ensure we read from the current position, respecting potential BOMs etc. + jsonNode = + await JsonSerializer + .DeserializeAsync( + configStream, + options.JsonSerializerOptions, + cancellationToken + ) + .ConfigureAwait(false) ?? new JsonObject(); + } + catch (JsonException ex) + { + // Handle cases where the file might exist but be invalid JSON + // Log the error, maybe throw a specific exception or return default + // For now, we'll treat it as empty/new + System.Diagnostics.Debug.WriteLine( + $"Error deserializing JSON config: {ex.Message}. Treating as new." + ); + jsonNode = new JsonObject(); + isEmpty = true; // Ensure we overwrite if deserialization failed + } + } + + UpdateJsonConfig(layout, jsonNode, pathsSelector, options); + + // Reset stream to original position (or beginning if new/failed) before writing + configStream.Seek(initialPosition, SeekOrigin.Begin); + // Truncate the stream in case the new content is shorter + configStream.SetLength(initialPosition + 0); // Truncate from the original position onwards + + await JsonSerializer + .SerializeAsync(configStream, jsonNode, options.JsonSerializerOptions, cancellationToken) + .ConfigureAwait(false); + await configStream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + private static void UpdateJsonConfig( + SharedFolderLayout layout, + JsonObject rootNode, // Changed parameter name for clarity + Func> pathsSelector, + ConfigSharingOptions options + ) + { + var rulesByConfigPath = layout.GetRulesByConfigPath(); + var allRuleConfigPaths = rulesByConfigPath.Keys.ToHashSet(); // For cleanup + + foreach (var (configPath, rule) in rulesByConfigPath) + { + var paths = pathsSelector(rule).ToArray(); + var normalizedPaths = paths.Select(p => p.Replace('\\', '/')).ToArray(); + + JsonNode? valueNode = null; + if (normalizedPaths.Length > 1 || options.AlwaysWriteArray) + { + valueNode = new JsonArray( + normalizedPaths.Select(p => JsonValue.Create(p)).OfType().ToArray() + ); + } + else if (normalizedPaths.Length == 1) + { + valueNode = JsonValue.Create(normalizedPaths[0]); + } + + SetJsonValue(rootNode, configPath, valueNode); // Use helper to set/remove value + } + + // Optional: Cleanup - Remove keys defined in layout but now empty? + // This might be complex if paths overlap. Current SetJsonValue(..., null) handles removal. + // We might need a separate cleanup pass if strictly necessary. + } + + private static void SetJsonValue(JsonObject root, string dottedPath, JsonNode? value) + { + var segments = dottedPath.Split('.'); + JsonObject currentNode = root; + + // Traverse or create nodes up to the parent of the target + for (int i = 0; i < segments.Length - 1; i++) + { + var segment = segments[i]; + if ( + !currentNode.TryGetPropertyValue(segment, out var nextNode) + || nextNode is not JsonObject nextObj + ) + { + // If node doesn't exist or isn't an object, create it (overwriting if necessary) + nextObj = new JsonObject(); + currentNode[segment] = nextObj; + } + currentNode = nextObj; + } + + var finalSegment = segments[^1]; // Get the last segment (the key name) + + if (value != null) + { + // Set or replace the value + currentNode[finalSegment] = value.DeepClone(); // Use DeepClone to avoid node reuse issues + } + else + { + // Remove the key if value is null + currentNode.Remove(finalSegment); + // Optional: Clean up empty parent nodes recursively if desired (more complex) + } + } +} diff --git a/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs new file mode 100644 index 00000000..88e2d721 --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs @@ -0,0 +1,254 @@ +using YamlDotNet.Core; +using YamlDotNet.RepresentationModel; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace StabilityMatrix.Core.Models.Packages.Config; + +public class YamlConfigSharingStrategy : IConfigSharingStrategy +{ + public async Task UpdateAndWriteAsync( + Stream configStream, + SharedFolderLayout layout, + Func> pathsSelector, + ConfigSharingOptions options, + CancellationToken cancellationToken = default + ) + { + YamlMappingNode rootNode; + YamlStream yamlStream = new(); + var initialPosition = configStream.Position; + var isEmpty = configStream.Length - initialPosition == 0; + + if (!isEmpty) + { + try + { + using var reader = new StreamReader(configStream, leaveOpen: true); + yamlStream.Load(reader); // Load existing YAML + if ( + yamlStream.Documents.Count > 0 + && yamlStream.Documents[0].RootNode is YamlMappingNode mapping + ) + { + rootNode = mapping; + } + else + { + // File exists but isn't a valid mapping node at the root, start fresh + System.Diagnostics.Debug.WriteLine( + $"YAML config exists but is not a mapping node. Treating as new." + ); + rootNode = new YamlMappingNode(); + yamlStream = new YamlStream(new YamlDocument(rootNode)); // Reset stream content + isEmpty = true; + } + } + catch (YamlException ex) + { + // Handle cases where the file might exist but be invalid YAML + System.Diagnostics.Debug.WriteLine( + $"Error deserializing YAML config: {ex.Message}. Treating as new." + ); + rootNode = new YamlMappingNode(); + yamlStream = new YamlStream(new YamlDocument(rootNode)); // Reset stream content + isEmpty = true; + } + } + else + { + // Stream is empty, create new structure + rootNode = new YamlMappingNode(); + yamlStream.Add(new YamlDocument(rootNode)); + } + + UpdateYamlConfig(layout, rootNode, pathsSelector, options); + + // Reset stream to original position (or beginning if new/failed) before writing + configStream.Seek(initialPosition, SeekOrigin.Begin); + // Truncate the stream in case the new content is shorter + configStream.SetLength(initialPosition + 0); + + // Use StreamWriter to write back to the original stream + // Use default encoding (UTF8 without BOM is common for YAML) + await using (var writer = new StreamWriter(configStream, leaveOpen: true)) + { + // Configure serializer for better readability if desired + var serializer = new SerializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) // Common for ComfyUI paths + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) // Optional: omit nulls/defaults + .Build(); + serializer.Serialize(writer, yamlStream.Documents[0].RootNode); // Serialize the modified root node + await writer.FlushAsync().ConfigureAwait(false); // Ensure content is written to stream + } + await configStream.FlushAsync(cancellationToken).ConfigureAwait(false); // Flush the underlying stream + } + + private static void UpdateYamlConfig( + SharedFolderLayout layout, + YamlMappingNode rootNode, + Func> pathsSelector, + ConfigSharingOptions options + ) + { + var rulesByConfigPath = layout.GetRulesByConfigPath(); + YamlNode currentNode = rootNode; // Start at the actual root + + // Handle RootKey (like stability_matrix) if specified + if (!string.IsNullOrEmpty(options.RootKey)) + { + var rootKeyNode = new YamlScalarNode(options.RootKey); + if ( + !rootNode.Children.TryGetValue(rootKeyNode, out var subNode) + || subNode is not YamlMappingNode subMapping + ) + { + if (subNode != null) + rootNode.Children.Remove(rootKeyNode); // Remove if exists but wrong type + subMapping = new YamlMappingNode(); + rootNode.Add(rootKeyNode, subMapping); + } + currentNode = subMapping; // Operate within the specified RootKey node + } + + if (currentNode is not YamlMappingNode writableNode) + { + // This should not happen if RootKey logic is correct, but handle defensively + System.Diagnostics.Debug.WriteLine($"Error: Target node for YAML updates is not a mapping node."); + return; + } + + foreach (var (configPath, rule) in rulesByConfigPath) + { + var paths = pathsSelector(rule).ToArray(); + var normalizedPaths = paths.Select(p => p.Replace('\\', '/')).ToArray(); + + YamlNode? valueNode = null; + if (normalizedPaths.Length > 0) + { + // Use Sequence for multiple paths + valueNode = new YamlSequenceNode( + normalizedPaths.Select(p => new YamlScalarNode(p)).Cast() + ); + // --- OR Use Literal Scalar --- + // var multiLinePath = string.Join("\n", normalizedPaths); + // valueNode = new YamlScalarNode(multiLinePath) { Style = ScalarStyle.Literal }; + } + + SetYamlValue(writableNode, configPath, valueNode); // Use helper + } + + // Optional: Cleanup empty nodes after setting values (could be complex) + } + + /*private static void UpdateYamlConfig( + SharedFolderLayout layout, + YamlMappingNode rootNode, + Func> pathsSelector, + ConfigSharingOptions options + ) + { + var rulesByConfigPath = layout.GetRulesByConfigPath(); + var smKeyNode = new YamlScalarNode(options.RootKey); + + // Find or create the root key node (e.g., "stability_matrix:") + if ( + !rootNode.Children.TryGetValue(smKeyNode, out var smPathsNode) + || smPathsNode is not YamlMappingNode smPathsMapping + ) + { + // If it exists but isn't a mapping, remove it first (or handle error) + if (smPathsNode != null) + { + rootNode.Children.Remove(smKeyNode); + } + smPathsMapping = new YamlMappingNode(); + rootNode.Add(smKeyNode, smPathsMapping); + } + + // Get all keys defined in the layout rules to manage removal later + var allRuleKeys = rulesByConfigPath.Keys.Select(k => new YamlScalarNode(k)).ToHashSet(); + var currentKeysInSmNode = smPathsMapping.Children.Keys.ToHashSet(); + + foreach (var (configPath, rule) in rulesByConfigPath) + { + var paths = pathsSelector(rule).ToArray(); + var keyNode = new YamlScalarNode(configPath); + + if (paths.Length > 0) + { + // Represent multiple paths as a YAML sequence (list) for clarity + // Normalize paths - YAML generally prefers forward slashes + var normalizedPaths = paths + .Select(p => new YamlScalarNode(p.Replace('\\', '/'))) + .Cast() + .ToList(); + smPathsMapping.Children[keyNode] = new YamlSequenceNode(normalizedPaths); + + // --- Alternatively, represent as multi-line literal scalar (like ComfyUI default) --- + // var multiLinePath = string.Join("\n", paths.Select(p => p.Replace('\\', '/'))); + // var valueNode = new YamlScalarNode(multiLinePath) { Style = ScalarStyle.Literal }; + // smPathsMapping.Children[keyNode] = valueNode; + // --------------------------------------------------------------------------------- + } + else + { + // No paths for this rule, remove the key from the SM node + smPathsMapping.Children.Remove(keyNode); + } + } + + // Remove any keys under the SM node that are no longer defined by any rule + foreach (var existingKey in currentKeysInSmNode) + { + if (!allRuleKeys.Any(ruleKey => ruleKey.Value == existingKey.ToString())) + { + smPathsMapping.Children.Remove(existingKey); + } + } + + // If the SM node becomes empty, remove it entirely + if (smPathsMapping.Children.Count == 0) + { + rootNode.Children.Remove(smKeyNode); + } + }*/ + + private static void SetYamlValue(YamlMappingNode rootMapping, string dottedPath, YamlNode? value) + { + var segments = dottedPath.Split('.'); + YamlMappingNode currentMapping = rootMapping; + + // Traverse or create nodes up to the parent of the target + for (int i = 0; i < segments.Length - 1; i++) + { + var segmentNode = new YamlScalarNode(segments[i]); + if ( + !currentMapping.Children.TryGetValue(segmentNode, out var nextNode) + || nextNode is not YamlMappingNode nextMapping + ) + { + // If node doesn't exist or isn't a mapping, create it + if (nextNode != null) + currentMapping.Children.Remove(segmentNode); // Remove if wrong type + nextMapping = new YamlMappingNode(); + currentMapping.Add(segmentNode, nextMapping); + } + currentMapping = nextMapping; + } + + var finalSegmentNode = new YamlScalarNode(segments[^1]); + + if (value != null) + { + // Set or replace the value + currentMapping.Children[finalSegmentNode] = value; + } + else + { + // Remove the key if value is null + currentMapping.Children.Remove(finalSegmentNode); + // Optional: Cleanup empty parent nodes recursively (more complex) + } + } +} diff --git a/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs b/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs index 2b121668..33ab245c 100644 --- a/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs +++ b/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using StabilityMatrix.Core.Models.Packages.Config; namespace StabilityMatrix.Core.Models.Packages; @@ -9,6 +10,10 @@ public record SharedFolderLayout /// public string? RelativeConfigPath { get; set; } + public ConfigFileType? ConfigFileType { get; set; } + + public ConfigSharingOptions ConfigSharingOptions { get; set; } = ConfigSharingOptions.Default; + public IImmutableList Rules { get; set; } = []; public Dictionary GetRulesByConfigPath() diff --git a/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs b/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs index 8b795bc2..93518484 100644 --- a/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs +++ b/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs @@ -8,17 +8,22 @@ public readonly record struct SharedFolderLayoutRule public string[] ConfigDocumentPaths { get; init; } + public bool IsRoot { get; init; } + public SharedFolderLayoutRule() { SourceTypes = []; TargetRelativePaths = []; ConfigDocumentPaths = []; + IsRoot = false; } public SharedFolderLayoutRule(SharedFolderType[] types, string[] targets) { SourceTypes = types; TargetRelativePaths = targets; + ConfigDocumentPaths = []; + IsRoot = false; } public SharedFolderLayoutRule(SharedFolderType[] types, string[] targets, string[] configs) @@ -26,6 +31,7 @@ public SharedFolderLayoutRule(SharedFolderType[] types, string[] targets, string SourceTypes = types; TargetRelativePaths = targets; ConfigDocumentPaths = configs; + IsRoot = false; } public SharedFolderLayoutRule Union(SharedFolderLayoutRule other) @@ -34,7 +40,8 @@ public SharedFolderLayoutRule Union(SharedFolderLayoutRule other) { SourceTypes = SourceTypes.Union(other.SourceTypes).ToArray(), TargetRelativePaths = TargetRelativePaths.Union(other.TargetRelativePaths).ToArray(), - ConfigDocumentPaths = ConfigDocumentPaths.Union(other.ConfigDocumentPaths).ToArray() + ConfigDocumentPaths = ConfigDocumentPaths.Union(other.ConfigDocumentPaths).ToArray(), + IsRoot = IsRoot || other.IsRoot }; } } From 30f4f1c71787c83d767e6f419428eb5c8b971a43 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 03:38:09 -0400 Subject: [PATCH 193/297] Add default config handling for BasePackage --- .../Models/Packages/BaseGitPackage.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index 9d070964..799f1349 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -617,6 +617,22 @@ public override async Task SetupModelFolders( SharedFolderMethod sharedFolderMethod ) { + // Auto handling for ISharedFolderLayoutPackage + if ( + sharedFolderMethod is SharedFolderMethod.Configuration + && this is ISharedFolderLayoutPackage layoutPackage + ) + { + await SharedFoldersConfigHelper + .UpdateConfigFileForSharedAsync( + layoutPackage.SharedFolderLayout, + installDirectory.FullPath, + SettingsManager.ModelsDirectory + ) + .ConfigureAwait(false); + return; + } + if (sharedFolderMethod != SharedFolderMethod.Symlink || SharedFolders is not { } sharedFolders) { return; @@ -667,7 +683,19 @@ public override Task RemoveModelFolderLinks( SharedFolderMethod sharedFolderMethod ) { - if (SharedFolders is not null && sharedFolderMethod == SharedFolderMethod.Symlink) + // Auto handling for ISharedFolderLayoutPackage + if ( + sharedFolderMethod is SharedFolderMethod.Configuration + && this is ISharedFolderLayoutPackage layoutPackage + ) + { + return SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync( + layoutPackage.SharedFolderLayout, + installDirectory.FullPath + ); + } + + if (SharedFolders is not null && sharedFolderMethod is SharedFolderMethod.Symlink) { Helper.SharedFolders.RemoveLinksForPackage(SharedFolders, installDirectory); } From d69a5afc1a91be7453acc52f4895ab6853cadadd Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 03:55:08 -0400 Subject: [PATCH 194/297] Change to literal and scalar for comfy --- .../Models/Packages/Config/YamlConfigSharingStrategy.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs index 88e2d721..8e6d0964 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs @@ -76,6 +76,7 @@ public async Task UpdateAndWriteAsync( // Configure serializer for better readability if desired var serializer = new SerializerBuilder() .WithNamingConvention(UnderscoredNamingConvention.Instance) // Common for ComfyUI paths + .WithDefaultScalarStyle(ScalarStyle.Literal) .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) // Optional: omit nulls/defaults .Build(); serializer.Serialize(writer, yamlStream.Documents[0].RootNode); // Serialize the modified root node @@ -127,12 +128,12 @@ ConfigSharingOptions options if (normalizedPaths.Length > 0) { // Use Sequence for multiple paths - valueNode = new YamlSequenceNode( + /*valueNode = new YamlSequenceNode( normalizedPaths.Select(p => new YamlScalarNode(p)).Cast() - ); + );*/ // --- OR Use Literal Scalar --- - // var multiLinePath = string.Join("\n", normalizedPaths); - // valueNode = new YamlScalarNode(multiLinePath) { Style = ScalarStyle.Literal }; + var multiLinePath = string.Join("\n", normalizedPaths); + valueNode = new YamlScalarNode(multiLinePath) { Style = ScalarStyle.Literal }; } SetYamlValue(writableNode, configPath, valueNode); // Use helper From 20174f2f0d3592ce1be43b1d0b253d7202fb724e Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 17:29:27 -0400 Subject: [PATCH 195/297] Add root handling --- .../Helper/SharedFoldersConfigHelper.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs index 6770fde7..a74281c4 100644 --- a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs +++ b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs @@ -71,9 +71,17 @@ await strategy stream, layout, rule => - rule.SourceTypes.Select(type => type.GetStringValue()) // Get the enum string value (e.g., "StableDiffusion") + { + // Handle Root, just use models directory (e.g., Swarm) + if (rule.IsRoot) + { + return [sharedModelsDirectory]; + } + + return rule.SourceTypes.Select(type => type.GetStringValue()) // Get the enum string value (e.g., "StableDiffusion") .Where(folderName => !string.IsNullOrEmpty(folderName)) // Filter out potentially empty mappings - .Select(folderName => Path.Combine(sharedModelsDirectory, folderName)), // Combine with base models dir + .Select(folderName => Path.Combine(sharedModelsDirectory, folderName)); // Combine with base models dir + }, options, cancellationToken ) From 91f3833d4f1553666cb27e0cdd688b4a830fa7af Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 18:53:54 -0400 Subject: [PATCH 196/297] Add support for SourceSubPath and Clear Root --- .../Helper/SharedFoldersConfigHelper.cs | 20 ++++++++++++- .../Packages/Config/ConfigDefaultType.cs | 14 +++++++++ .../Packages/Config/ConfigSharingOptions.cs | 3 ++ .../Config/FdsConfigSharingStrategy.cs | 8 +++-- .../Packages/Config/IConfigSharingStrategy.cs | 1 + .../Config/JsonConfigSharingStrategy.cs | 4 ++- .../Config/YamlConfigSharingStrategy.cs | 30 ++++++++++++------- .../Models/Packages/SharedFolderLayout.cs | 2 ++ .../Models/Packages/SharedFolderLayoutRule.cs | 8 +++++ 9 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 StabilityMatrix.Core/Models/Packages/Config/ConfigDefaultType.cs diff --git a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs index a74281c4..f2fc5e69 100644 --- a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs +++ b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs @@ -78,10 +78,19 @@ await strategy return [sharedModelsDirectory]; } - return rule.SourceTypes.Select(type => type.GetStringValue()) // Get the enum string value (e.g., "StableDiffusion") + var paths = rule.SourceTypes.Select(type => type.GetStringValue()) // Get the enum string value (e.g., "StableDiffusion") .Where(folderName => !string.IsNullOrEmpty(folderName)) // Filter out potentially empty mappings .Select(folderName => Path.Combine(sharedModelsDirectory, folderName)); // Combine with base models dir + + // If sub-path provided, add to all paths + if (!string.IsNullOrEmpty(rule.SourceSubPath)) + { + paths = paths.Select(path => Path.Combine(path, rule.SourceSubPath)); + } + + return paths; }, + [], options, cancellationToken ) @@ -135,6 +144,14 @@ public static async Task UpdateConfigFileForDefaultAsync( throw new NotSupportedException($"Configuration file type '{fileType}' is not supported."); } + var clearPaths = new List(); + + // If using clear root option, add the root key + if (options.ConfigDefaultType is ConfigDefaultType.ClearRoot) + { + clearPaths.Add(options.RootKey ?? ""); + } + await strategy .UpdateAndWriteAsync( stream, @@ -143,6 +160,7 @@ await strategy rule.TargetRelativePaths.Select( path => Path.Combine(packageRootDirectory, NormalizePathSlashes(path)) ), // Combine relative with package root + clearPaths, options, cancellationToken ) diff --git a/StabilityMatrix.Core/Models/Packages/Config/ConfigDefaultType.cs b/StabilityMatrix.Core/Models/Packages/Config/ConfigDefaultType.cs new file mode 100644 index 00000000..6728b59e --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/Config/ConfigDefaultType.cs @@ -0,0 +1,14 @@ +namespace StabilityMatrix.Core.Models.Packages.Config; + +public enum ConfigDefaultType +{ + /// + /// Set as SharedFolderLayout.TargetRelativePaths. + /// + TargetRelativePaths, + + /// + /// Clear the root key when defaulting. + /// + ClearRoot, +} diff --git a/StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs b/StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs index ff2df697..8138bb28 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs @@ -15,4 +15,7 @@ public record ConfigSharingOptions // For YAML/FDS: Key under which to store SM paths (e.g., "stability_matrix") public string? RootKey { get; set; } + + // Do we want to clear the root key / set to relative paths when clearing? + public ConfigDefaultType ConfigDefaultType { get; set; } = ConfigDefaultType.TargetRelativePaths; } diff --git a/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs index 44070ab3..cd988e45 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs @@ -8,6 +8,7 @@ public async Task UpdateAndWriteAsync( Stream configStream, SharedFolderLayout layout, Func> pathsSelector, + IEnumerable clearPaths, ConfigSharingOptions options, CancellationToken cancellationToken = default ) @@ -22,7 +23,7 @@ public async Task UpdateAndWriteAsync( { // FDSUtility reads from the current position using var reader = new StreamReader(configStream, leaveOpen: true); - var fdsContent = await reader.ReadToEndAsync().ConfigureAwait(false); + var fdsContent = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); rootSection = new FDSSection(fdsContent); } catch (Exception ex) // FDSUtility might throw various exceptions on parse errors @@ -39,7 +40,7 @@ public async Task UpdateAndWriteAsync( rootSection = new FDSSection(); } - UpdateFdsConfig(layout, rootSection, pathsSelector, options); + UpdateFdsConfig(layout, rootSection, pathsSelector, clearPaths, options); // Reset stream to original position before writing configStream.Seek(initialPosition, SeekOrigin.Begin); @@ -52,7 +53,7 @@ public async Task UpdateAndWriteAsync( await writer .WriteAsync(rootSection.SaveToString().AsMemory(), cancellationToken) .ConfigureAwait(false); - await writer.FlushAsync().ConfigureAwait(false); // Ensure content is written + await writer.FlushAsync(cancellationToken).ConfigureAwait(false); // Ensure content is written } await configStream.FlushAsync(cancellationToken).ConfigureAwait(false); // Flush the underlying stream } @@ -61,6 +62,7 @@ private static void UpdateFdsConfig( SharedFolderLayout layout, FDSSection rootSection, Func> pathsSelector, + IEnumerable clearPaths, ConfigSharingOptions options ) { diff --git a/StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs index b1d6c01b..c116d50a 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs @@ -9,6 +9,7 @@ Task UpdateAndWriteAsync( Stream configStream, SharedFolderLayout layout, Func> pathsSelector, + IEnumerable clearPaths, ConfigSharingOptions options, CancellationToken cancellationToken = default ); diff --git a/StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs index 94ea37d0..e2e2ce67 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs @@ -9,6 +9,7 @@ public async Task UpdateAndWriteAsync( Stream configStream, SharedFolderLayout layout, Func> pathsSelector, + IEnumerable clearPaths, ConfigSharingOptions options, CancellationToken cancellationToken = default ) @@ -48,7 +49,7 @@ await JsonSerializer } } - UpdateJsonConfig(layout, jsonNode, pathsSelector, options); + UpdateJsonConfig(layout, jsonNode, pathsSelector, clearPaths, options); // Reset stream to original position (or beginning if new/failed) before writing configStream.Seek(initialPosition, SeekOrigin.Begin); @@ -65,6 +66,7 @@ private static void UpdateJsonConfig( SharedFolderLayout layout, JsonObject rootNode, // Changed parameter name for clarity Func> pathsSelector, + IEnumerable clearPaths, ConfigSharingOptions options ) { diff --git a/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs index 8e6d0964..dcf4c180 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs @@ -11,12 +11,13 @@ public async Task UpdateAndWriteAsync( Stream configStream, SharedFolderLayout layout, Func> pathsSelector, + IEnumerable clearPaths, ConfigSharingOptions options, CancellationToken cancellationToken = default ) { YamlMappingNode rootNode; - YamlStream yamlStream = new(); + YamlStream yamlStream = []; var initialPosition = configStream.Position; var isEmpty = configStream.Length - initialPosition == 0; @@ -39,7 +40,7 @@ public async Task UpdateAndWriteAsync( System.Diagnostics.Debug.WriteLine( $"YAML config exists but is not a mapping node. Treating as new." ); - rootNode = new YamlMappingNode(); + rootNode = []; yamlStream = new YamlStream(new YamlDocument(rootNode)); // Reset stream content isEmpty = true; } @@ -50,7 +51,7 @@ public async Task UpdateAndWriteAsync( System.Diagnostics.Debug.WriteLine( $"Error deserializing YAML config: {ex.Message}. Treating as new." ); - rootNode = new YamlMappingNode(); + rootNode = []; yamlStream = new YamlStream(new YamlDocument(rootNode)); // Reset stream content isEmpty = true; } @@ -58,11 +59,11 @@ public async Task UpdateAndWriteAsync( else { // Stream is empty, create new structure - rootNode = new YamlMappingNode(); + rootNode = []; yamlStream.Add(new YamlDocument(rootNode)); } - UpdateYamlConfig(layout, rootNode, pathsSelector, options); + UpdateYamlConfig(layout, rootNode, pathsSelector, clearPaths, options); // Reset stream to original position (or beginning if new/failed) before writing configStream.Seek(initialPosition, SeekOrigin.Begin); @@ -80,7 +81,7 @@ public async Task UpdateAndWriteAsync( .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) // Optional: omit nulls/defaults .Build(); serializer.Serialize(writer, yamlStream.Documents[0].RootNode); // Serialize the modified root node - await writer.FlushAsync().ConfigureAwait(false); // Ensure content is written to stream + await writer.FlushAsync(cancellationToken).ConfigureAwait(false); // Ensure content is written to stream } await configStream.FlushAsync(cancellationToken).ConfigureAwait(false); // Flush the underlying stream } @@ -89,6 +90,7 @@ private static void UpdateYamlConfig( SharedFolderLayout layout, YamlMappingNode rootNode, Func> pathsSelector, + IEnumerable clearPaths, ConfigSharingOptions options ) { @@ -106,7 +108,7 @@ ConfigSharingOptions options { if (subNode != null) rootNode.Children.Remove(rootKeyNode); // Remove if exists but wrong type - subMapping = new YamlMappingNode(); + subMapping = []; rootNode.Add(rootKeyNode, subMapping); } currentNode = subMapping; // Operate within the specified RootKey node @@ -131,7 +133,7 @@ ConfigSharingOptions options /*valueNode = new YamlSequenceNode( normalizedPaths.Select(p => new YamlScalarNode(p)).Cast() );*/ - // --- OR Use Literal Scalar --- + // --- Multi-line literal scalar (ComfyUI default) --- var multiLinePath = string.Join("\n", normalizedPaths); valueNode = new YamlScalarNode(multiLinePath) { Style = ScalarStyle.Literal }; } @@ -139,6 +141,12 @@ ConfigSharingOptions options SetYamlValue(writableNode, configPath, valueNode); // Use helper } + // Clear specified paths + foreach (var clearPath in clearPaths) + { + SetYamlValue(rootNode, clearPath, null); // Note we use root node here instead + } + // Optional: Cleanup empty nodes after setting values (could be complex) } @@ -218,10 +226,10 @@ ConfigSharingOptions options private static void SetYamlValue(YamlMappingNode rootMapping, string dottedPath, YamlNode? value) { var segments = dottedPath.Split('.'); - YamlMappingNode currentMapping = rootMapping; + var currentMapping = rootMapping; // Traverse or create nodes up to the parent of the target - for (int i = 0; i < segments.Length - 1; i++) + for (var i = 0; i < segments.Length - 1; i++) { var segmentNode = new YamlScalarNode(segments[i]); if ( @@ -232,7 +240,7 @@ private static void SetYamlValue(YamlMappingNode rootMapping, string dottedPath, // If node doesn't exist or isn't a mapping, create it if (nextNode != null) currentMapping.Children.Remove(segmentNode); // Remove if wrong type - nextMapping = new YamlMappingNode(); + nextMapping = []; currentMapping.Add(segmentNode, nextMapping); } currentMapping = nextMapping; diff --git a/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs b/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs index 33ab245c..7431407c 100644 --- a/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs +++ b/StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs @@ -1,8 +1,10 @@ using System.Collections.Immutable; +using System.ComponentModel; using StabilityMatrix.Core.Models.Packages.Config; namespace StabilityMatrix.Core.Models.Packages; +[Localizable(false)] public record SharedFolderLayout { /// diff --git a/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs b/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs index 93518484..dc0d617c 100644 --- a/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs +++ b/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs @@ -8,8 +8,16 @@ public readonly record struct SharedFolderLayoutRule public string[] ConfigDocumentPaths { get; init; } + /// + /// For rules that use the root models folder instead of a specific SharedFolderType + /// public bool IsRoot { get; init; } + /// + /// Optional sub-path from all source types to the target path. + /// + public string? SourceSubPath { get; init; } + public SharedFolderLayoutRule() { SourceTypes = []; From 89db1db7cf85a5c39411d4e647bec9a13f26bad7 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 18:54:27 -0400 Subject: [PATCH 197/297] Move FolderLayout to BasePackage --- .../Models/Packages/BaseGitPackage.cs | 79 ++++++++----------- .../Models/Packages/BasePackage.cs | 76 +++++++++++++++++- .../Packages/ISharedFolderLayoutPackage.cs | 49 ------------ 3 files changed, 108 insertions(+), 96 deletions(-) delete mode 100644 StabilityMatrix.Core/Models/Packages/ISharedFolderLayoutPackage.cs diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index 799f1349..e7349d96 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -617,60 +617,52 @@ public override async Task SetupModelFolders( SharedFolderMethod sharedFolderMethod ) { - // Auto handling for ISharedFolderLayoutPackage - if ( - sharedFolderMethod is SharedFolderMethod.Configuration - && this is ISharedFolderLayoutPackage layoutPackage - ) + if (sharedFolderMethod is SharedFolderMethod.Configuration && SharedFolderLayout is not null) { await SharedFoldersConfigHelper .UpdateConfigFileForSharedAsync( - layoutPackage.SharedFolderLayout, + SharedFolderLayout, installDirectory.FullPath, SettingsManager.ModelsDirectory ) .ConfigureAwait(false); - return; } - - if (sharedFolderMethod != SharedFolderMethod.Symlink || SharedFolders is not { } sharedFolders) + else if (sharedFolderMethod is SharedFolderMethod.Symlink && SharedFolders is { } sharedFolders) { - return; - } + var modelsDir = new DirectoryPath(SettingsManager.ModelsDirectory); - var modelsDir = new DirectoryPath(SettingsManager.ModelsDirectory); + // fix infinity controlnet folders + await FixInfinityFolders(modelsDir.JoinDir("ControlNet"), "ControlNet").ConfigureAwait(false); + await FixForgeInfinity().ConfigureAwait(false); - // fix infinity controlnet folders - await FixInfinityFolders(modelsDir.JoinDir("ControlNet"), "ControlNet").ConfigureAwait(false); - await FixForgeInfinity().ConfigureAwait(false); + // fix duplicate links in models dir + // see https://github.com/LykosAI/StabilityMatrix/issues/338 + string[] duplicatePaths = + [ + Path.Combine("ControlNet", "ControlNet"), + Path.Combine("IPAdapter", "base"), + Path.Combine("IPAdapter", "sd15"), + Path.Combine("IPAdapter", "sdxl") + ]; - // fix duplicate links in models dir - // see https://github.com/LykosAI/StabilityMatrix/issues/338 - string[] duplicatePaths = - [ - Path.Combine("ControlNet", "ControlNet"), - Path.Combine("IPAdapter", "base"), - Path.Combine("IPAdapter", "sd15"), - Path.Combine("IPAdapter", "sdxl") - ]; + foreach (var duplicatePath in duplicatePaths) + { + var linkDir = modelsDir.JoinDir(duplicatePath); + if (!linkDir.IsSymbolicLink) + continue; - foreach (var duplicatePath in duplicatePaths) - { - var linkDir = modelsDir.JoinDir(duplicatePath); - if (!linkDir.IsSymbolicLink) - continue; + Logger.Info("Removing duplicate junction at {Path}", linkDir.ToString()); + await linkDir.DeleteAsync(false).ConfigureAwait(false); + } - Logger.Info("Removing duplicate junction at {Path}", linkDir.ToString()); - await linkDir.DeleteAsync(false).ConfigureAwait(false); + await Helper + .SharedFolders.UpdateLinksForPackage( + sharedFolders, + SettingsManager.ModelsDirectory, + installDirectory + ) + .ConfigureAwait(false); } - - await Helper - .SharedFolders.UpdateLinksForPackage( - sharedFolders, - SettingsManager.ModelsDirectory, - installDirectory - ) - .ConfigureAwait(false); } public override Task UpdateModelFolders( @@ -683,14 +675,11 @@ public override Task RemoveModelFolderLinks( SharedFolderMethod sharedFolderMethod ) { - // Auto handling for ISharedFolderLayoutPackage - if ( - sharedFolderMethod is SharedFolderMethod.Configuration - && this is ISharedFolderLayoutPackage layoutPackage - ) + // Auto handling for SharedFolderLayout + if (sharedFolderMethod is SharedFolderMethod.Configuration && SharedFolderLayout is not null) { return SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync( - layoutPackage.SharedFolderLayout, + SharedFolderLayout, installDirectory.FullPath ); } diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs index 448fcc85..ac9e7722 100644 --- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs @@ -1,5 +1,7 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using Octokit; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.Database; @@ -184,11 +186,81 @@ public virtual TorchIndex GetRecommendedTorchVersion() public abstract List LaunchOptions { get; } public virtual IReadOnlyList ExtraLaunchArguments { get; } = Array.Empty(); + /// + /// Layout of the shared folders. For both Symlink and Config. + /// + public virtual SharedFolderLayout? SharedFolderLayout { get; } = new(); + /// /// The shared folders that this package supports. /// Mapping of to the relative paths from the package root. + /// (Legacy format for Symlink only, computed from SharedFolderLayout.) + /// + public virtual Dictionary>? SharedFolders => + GetLegacySharedFolders(); + + private Dictionary>? GetLegacySharedFolders() + { + if (SharedFolderLayout is null) + return null; + + // Keep track of unique paths since symbolic links can't do multiple targets + // So we'll ignore duplicates once they appear here + var addedPaths = new HashSet(); + var result = new Dictionary>(); + + foreach (var rule in SharedFolderLayout.Rules) + { + // Ignore empty + if (rule.TargetRelativePaths is not { Length: > 0 }) + { + continue; + } + + // If there are multi SourceTypes <-> TargetRelativePaths: + // We'll add a sub-path later + var isMultiSource = rule.SourceTypes.Length > 1; + + foreach (var folderTypeKey in rule.SourceTypes) + { + var existingList = + (ImmutableList) + result.GetValueOrDefault(folderTypeKey, ImmutableList.Empty); + + var folderName = folderTypeKey.GetStringValue(); + + foreach (var path in rule.TargetRelativePaths) + { + var currentPath = path; + + if (isMultiSource) + { + // Add a sub-path for each source type + currentPath = $"{path}/{folderName}"; + } + + // Skip if the path is already in the list + if (existingList.Contains(currentPath)) + continue; + + // Skip if the path is already added globally + if (!addedPaths.Add(currentPath)) + continue; + + result[folderTypeKey] = existingList.Add(currentPath); + } + } + } + + return result; + } + + /// + /// Represents a mapping of shared output types to their corresponding folder paths. + /// This property defines where various output files, such as images or grids, + /// are stored for the package. The dictionary keys represent specific + /// output types, and the values are lists of associated folder paths. /// - public abstract Dictionary>? SharedFolders { get; } public abstract Dictionary>? SharedOutputFolders { get; } /// diff --git a/StabilityMatrix.Core/Models/Packages/ISharedFolderLayoutPackage.cs b/StabilityMatrix.Core/Models/Packages/ISharedFolderLayoutPackage.cs deleted file mode 100644 index 58d1e9a0..00000000 --- a/StabilityMatrix.Core/Models/Packages/ISharedFolderLayoutPackage.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Immutable; - -namespace StabilityMatrix.Core.Models.Packages; - -public interface ISharedFolderLayoutPackage -{ - SharedFolderLayout SharedFolderLayout { get; } - - Dictionary> LegacySharedFolders - { - get - { - // Keep track of unique paths since symbolic links can't do multiple targets - // So we'll ignore duplicates once they appear here - var addedPaths = new HashSet(); - var result = new Dictionary>(); - - foreach (var rule in SharedFolderLayout.Rules) - { - if (rule.TargetRelativePaths is not { Length: > 0 } value) - { - continue; - } - - foreach (var folderTypeKey in rule.SourceTypes) - { - var existingList = - (ImmutableList) - result.GetValueOrDefault(folderTypeKey, ImmutableList.Empty); - - foreach (var path in value) - { - // Skip if the path is already in the list - if (existingList.Contains(path)) - continue; - - // Skip if the path is already added globally - if (!addedPaths.Add(path)) - continue; - - result[folderTypeKey] = existingList.Add(path); - } - } - } - - return result; - } - } -} From ee68b6ef400a1764df6c390f00b5a1ae798a9f7a Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 18:54:49 -0400 Subject: [PATCH 198/297] Remove empty cogstudio shared folder config --- StabilityMatrix.Core/Models/Packages/Cogstudio.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/Cogstudio.cs b/StabilityMatrix.Core/Models/Packages/Cogstudio.cs index 7d6d3408..0156d066 100644 --- a/StabilityMatrix.Core/Models/Packages/Cogstudio.cs +++ b/StabilityMatrix.Core/Models/Packages/Cogstudio.cs @@ -16,9 +16,7 @@ public class Cogstudio( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper -) - : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper), - ISharedFolderLayoutPackage +) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper) { public override string Name => "Cogstudio"; public override string DisplayName { get; set; } = "Cogstudio"; @@ -36,9 +34,6 @@ IPrerequisiteHelper prerequisiteHelper public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None; public override IEnumerable AvailableSharedFolderMethods => new[] { SharedFolderMethod.None }; - public override Dictionary> SharedFolders => - ((ISharedFolderLayoutPackage)this).LegacySharedFolders; - public virtual SharedFolderLayout SharedFolderLayout => new(); public override Dictionary> SharedOutputFolders => new() { [SharedOutputType.Text2Vid] = new[] { "output" } }; public override IEnumerable AvailableTorchIndices => From d5d9542bab7d518831a8c47fd58d48c6356fbecd Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:09:36 -0400 Subject: [PATCH 199/297] Add stream overloads, unit tests --- .../Helper/SharedFoldersConfigHelper.cs | 81 +-- .../Packages/SharedFolderConfigHelperTests.cs | 538 ++++++++++++++++++ 2 files changed, 581 insertions(+), 38 deletions(-) create mode 100644 StabilityMatrix.Tests/Models/Packages/SharedFolderConfigHelperTests.cs diff --git a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs index f2fc5e69..854c966e 100644 --- a/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs +++ b/StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs @@ -16,23 +16,6 @@ public static class SharedFoldersConfigHelper // Add more strategies here as needed }; - public static Task UpdateConfigFileForSharedAsync( - SharedFolderLayout layout, - string packageRootDirectory, - string sharedModelsDirectory, - CancellationToken cancellationToken = default - ) - { - return UpdateConfigFileForSharedAsync( - layout, - packageRootDirectory, - sharedModelsDirectory, - layout.ConfigFileType ?? throw new InvalidOperationException("ConfigFileType is null"), - layout.ConfigSharingOptions, - cancellationToken - ); - } - /// /// Updates a config file with shared folder layout rules, using the SourceTypes, /// converted to absolute paths using the sharedModelsDirectory. @@ -41,12 +24,9 @@ public static async Task UpdateConfigFileForSharedAsync( SharedFolderLayout layout, string packageRootDirectory, string sharedModelsDirectory, - ConfigFileType fileType, // Specify the file type - ConfigSharingOptions? options = null, CancellationToken cancellationToken = default ) { - options ??= ConfigSharingOptions.Default; var configPath = Path.Combine( packageRootDirectory, layout.RelativeConfigPath ?? throw new InvalidOperationException("RelativeConfigPath is null") @@ -61,6 +41,31 @@ public static async Task UpdateConfigFileForSharedAsync( FileShare.None ); + await UpdateConfigFileForSharedAsync( + layout, + packageRootDirectory, + sharedModelsDirectory, + stream, + cancellationToken + ) + .ConfigureAwait(false); + } + + /// + /// Updates a config file with shared folder layout rules, using the SourceTypes, + /// converted to absolute paths using the sharedModelsDirectory. + /// + public static async Task UpdateConfigFileForSharedAsync( + SharedFolderLayout layout, + string packageRootDirectory, + string sharedModelsDirectory, + Stream stream, + CancellationToken cancellationToken = default + ) + { + var fileType = layout.ConfigFileType ?? throw new InvalidOperationException("ConfigFileType is null"); + var options = layout.ConfigSharingOptions; + if (!Strategies.TryGetValue(fileType, out var strategy)) { throw new NotSupportedException($"Configuration file type '{fileType}' is not supported."); @@ -97,21 +102,6 @@ await strategy .ConfigureAwait(false); } - public static Task UpdateConfigFileForDefaultAsync( - SharedFolderLayout layout, - string packageRootDirectory, - CancellationToken cancellationToken = default - ) - { - return UpdateConfigFileForDefaultAsync( - layout, - packageRootDirectory, - layout.ConfigFileType ?? throw new InvalidOperationException("ConfigFileType is null"), - layout.ConfigSharingOptions, - cancellationToken - ); - } - /// /// Updates a config file with shared folder layout rules, using the TargetRelativePaths, /// converted to absolute paths using the packageRootDirectory (restores default paths). @@ -119,12 +109,9 @@ public static Task UpdateConfigFileForDefaultAsync( public static async Task UpdateConfigFileForDefaultAsync( SharedFolderLayout layout, string packageRootDirectory, - ConfigFileType fileType, // Specify the file type - ConfigSharingOptions? options = null, CancellationToken cancellationToken = default ) { - options ??= ConfigSharingOptions.Default; var configPath = Path.Combine( packageRootDirectory, layout.RelativeConfigPath ?? throw new InvalidOperationException("RelativeConfigPath is null") @@ -139,6 +126,24 @@ public static async Task UpdateConfigFileForDefaultAsync( FileShare.None ); + await UpdateConfigFileForDefaultAsync(layout, packageRootDirectory, stream, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Updates a config file with shared folder layout rules, using the TargetRelativePaths, + /// converted to absolute paths using the packageRootDirectory (restores default paths). + /// + public static async Task UpdateConfigFileForDefaultAsync( + SharedFolderLayout layout, + string packageRootDirectory, + Stream stream, + CancellationToken cancellationToken = default + ) + { + var fileType = layout.ConfigFileType ?? throw new InvalidOperationException("ConfigFileType is null"); + var options = layout.ConfigSharingOptions; + if (!Strategies.TryGetValue(fileType, out var strategy)) { throw new NotSupportedException($"Configuration file type '{fileType}' is not supported."); diff --git a/StabilityMatrix.Tests/Models/Packages/SharedFolderConfigHelperTests.cs b/StabilityMatrix.Tests/Models/Packages/SharedFolderConfigHelperTests.cs new file mode 100644 index 00000000..3d38dca0 --- /dev/null +++ b/StabilityMatrix.Tests/Models/Packages/SharedFolderConfigHelperTests.cs @@ -0,0 +1,538 @@ +using System.Collections.Immutable; +using System.Text; +using System.Text.Json.Nodes; +using FreneticUtilities.FreneticDataSyntax; +using StabilityMatrix.Core.Extensions; +using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.Packages; +using StabilityMatrix.Core.Models.Packages.Config; +using YamlDotNet.RepresentationModel; + +namespace StabilityMatrix.Tests.Models.Packages; + +[TestClass] +public class SharedFoldersConfigHelperTests +{ + // Define mock paths used across tests + private const string MockPackageRoot = @"C:\SM\Packages\TestPackage"; // Use OS-specific or normalized + private const string MockSharedModelsRoot = @"C:\SM\Models"; + + // Helper to run the target method and return the resulting stream content as string + private async Task RunHelperAndGetOutput( + SharedFolderLayout layout, + string packageRoot, + string sharedModelsRoot, + bool useSharedMode // True for SharedAsync, False for DefaultAsync + ) + { + using var stream = new MemoryStream(); + + if (useSharedMode) + { + await SharedFoldersConfigHelper.UpdateConfigFileForSharedAsync( + layout, + packageRoot, + sharedModelsRoot, + stream + ); + } + else + { + await SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync(layout, packageRoot, stream); + } + + stream.Position = 0; // Rewind stream to read the output + using var reader = new StreamReader(stream, Encoding.UTF8); + return await reader.ReadToEndAsync(); + } + + // Helper to normalize paths in expected strings for cross-platform compatibility + private string NormalizeExpectedPath(string path) => path.Replace('/', Path.DirectorySeparatorChar); + + // --- JSON Tests --- + + [TestMethod] + public async Task Json_UpdateForShared_WritesCorrectPaths() + { + // Arrange + var layout = new SharedFolderLayout + { + RelativeConfigPath = "config.json", + ConfigFileType = ConfigFileType.Json, + ConfigSharingOptions = ConfigSharingOptions.Default, // Use default options + Rules = ImmutableList.Create( + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.StableDiffusion], + ConfigDocumentPaths = ["ckpt_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS], + ConfigDocumentPaths = ["lora_dirs"] + } // Test multiple sources -> array + ) + }; + + // Act + var outputJson = await RunHelperAndGetOutput( + layout, + MockPackageRoot, + MockSharedModelsRoot, + useSharedMode: true + ); + var jsonNode = JsonNode.Parse(outputJson); + + // Assert + Assert.IsNotNull(jsonNode); + var expectedCkptPath = Path.Combine(MockSharedModelsRoot, "StableDiffusion").Replace('\\', '/'); // JSON usually uses / + var expectedLoraPath = Path.Combine(MockSharedModelsRoot, "Lora").Replace('\\', '/'); + var expectedLycoPath = Path.Combine(MockSharedModelsRoot, "LyCORIS").Replace('\\', '/'); + + Assert.AreEqual(expectedCkptPath, jsonNode["ckpt_dir"]?.GetValue()); + + var loraDirs = jsonNode["lora_dirs"] as JsonArray; + Assert.IsNotNull(loraDirs); + Assert.AreEqual(2, loraDirs.Count); + Assert.IsTrue(loraDirs.Any(n => n != null && n.GetValue() == expectedLoraPath)); + Assert.IsTrue(loraDirs.Any(n => n != null && n.GetValue() == expectedLycoPath)); + } + + [TestMethod] + public async Task Json_UpdateForDefault_WritesCorrectPaths() + { + // Arrange + var layout = new SharedFolderLayout + { + RelativeConfigPath = "config.json", + ConfigFileType = ConfigFileType.Json, + ConfigSharingOptions = ConfigSharingOptions.Default, + Rules = ImmutableList.Create( + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.StableDiffusion], + TargetRelativePaths = ["models/checkpoints"], + ConfigDocumentPaths = ["ckpt_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora], + TargetRelativePaths = ["models/loras"], + ConfigDocumentPaths = ["lora_dirs"] + } // Assume single default path + ) + }; + var expectedCkptPath = Path.Combine(MockPackageRoot, "models", "checkpoints").Replace('\\', '/'); + var expectedLoraPath = Path.Combine(MockPackageRoot, "models", "loras").Replace('\\', '/'); + + // Act + var outputJson = await RunHelperAndGetOutput( + layout, + MockPackageRoot, + MockSharedModelsRoot, + useSharedMode: false + ); // Default Mode + var jsonNode = JsonNode.Parse(outputJson); + + // Assert + Assert.IsNotNull(jsonNode); + Assert.AreEqual(expectedCkptPath, jsonNode["ckpt_dir"]?.GetValue()); + // Since default writes single target path, expect string, not array + Assert.AreEqual(expectedLoraPath, jsonNode["lora_dirs"]?.GetValue()); + } + + [TestMethod] + public async Task Json_NestedPaths_UpdateForShared_WritesCorrectly() + { + // Arrange + var layout = new SharedFolderLayout + { + RelativeConfigPath = "config.json", + ConfigFileType = ConfigFileType.Json, + ConfigSharingOptions = ConfigSharingOptions.Default, + Rules = ImmutableList.Create( + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.VAE], + ConfigDocumentPaths = ["paths.models.vae"] + } + ) + }; + var expectedVaePath = Path.Combine(MockSharedModelsRoot, "VAE").Replace('\\', '/'); + + // Act + var outputJson = await RunHelperAndGetOutput( + layout, + MockPackageRoot, + MockSharedModelsRoot, + useSharedMode: true + ); + var jsonNode = JsonNode.Parse(outputJson); + + // Assert + Assert.IsNotNull(jsonNode); + Assert.AreEqual(expectedVaePath, jsonNode?["paths"]?["models"]?["vae"]?.GetValue()); + } + + // --- YAML Tests --- + + [TestMethod] + public async Task Yaml_UpdateForShared_WritesCorrectPathsWithRootKey() + { + // Arrange + var layout = new SharedFolderLayout + { + RelativeConfigPath = "extra_paths.yaml", + ConfigFileType = ConfigFileType.Yaml, + ConfigSharingOptions = ConfigSharingOptions.Default with { RootKey = "stability_matrix" }, // Set RootKey + Rules = ImmutableList.Create( + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.VAE], + ConfigDocumentPaths = ["vae"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS], + ConfigDocumentPaths = ["loras"] + } + ) + }; + var expectedVaePath = Path.Combine(MockSharedModelsRoot, "VAE").Replace('\\', '/'); + var expectedLoraPath = Path.Combine(MockSharedModelsRoot, "Lora").Replace('\\', '/'); + var expectedLycoPath = Path.Combine(MockSharedModelsRoot, "LyCORIS").Replace('\\', '/'); + + // Act + var outputYaml = await RunHelperAndGetOutput( + layout, + MockPackageRoot, + MockSharedModelsRoot, + useSharedMode: true + ); + + // Assert using YamlDotNet.RepresentationModel + var yamlStream = new YamlStream(); + yamlStream.Load(new StringReader(outputYaml)); + var rootMapping = yamlStream.Documents[0].RootNode as YamlMappingNode; + Assert.IsNotNull(rootMapping); + + var smNode = rootMapping.Children[new YamlScalarNode("stability_matrix")] as YamlMappingNode; + Assert.IsNotNull(smNode); + + // Scalars + var vaeNode = smNode.Children[new YamlScalarNode("vae")] as YamlScalarNode; + Assert.IsNotNull(vaeNode); + Assert.AreEqual(expectedVaePath, vaeNode.Value); + + var lorasNode = smNode.Children[new YamlScalarNode("loras")] as YamlScalarNode; + Assert.IsNotNull(lorasNode); + // Split into sequences + var loras = lorasNode.Value?.SplitLines() ?? []; + CollectionAssert.Contains(loras, expectedLoraPath); + CollectionAssert.Contains(loras, expectedLycoPath); + + // Sequence support + /*var vaeNode = smNode.Children[new YamlScalarNode("vae")] as YamlSequenceNode; + Assert.IsNotNull(vaeNode); + Assert.AreEqual(1, vaeNode.Children.Count); + Assert.AreEqual(expectedVaePath, (vaeNode.Children[0] as YamlScalarNode)?.Value); + + var lorasNode = smNode.Children[new YamlScalarNode("loras")] as YamlSequenceNode; + Assert.IsNotNull(lorasNode); + Assert.AreEqual(2, lorasNode.Children.Count); + Assert.IsTrue(lorasNode.Children.Any(n => n is YamlScalarNode ns && ns.Value == expectedLoraPath)); + Assert.IsTrue(lorasNode.Children.Any(n => n is YamlScalarNode ns && ns.Value == expectedLycoPath));*/ + } + + [TestMethod] + public async Task Yaml_UpdateForDefault_RelativePaths() + { + // Arrange + var initialYamlContent = """ + # Existing content + some_other_key: value + stability_matrix: + vae: + - C:\SM\Models/VAE + loras: + - C:\SM\Models/Lora + - C:\SM\Models/LyCORIS + another_key: 123 + """; + var layout = new SharedFolderLayout + { + RelativeConfigPath = "extra_paths.yaml", + ConfigFileType = ConfigFileType.Yaml, + ConfigSharingOptions = ConfigSharingOptions.Default with + { + RootKey = "stability_matrix", + ConfigDefaultType = ConfigDefaultType.TargetRelativePaths // Configure relative paths + }, + Rules = ImmutableList.Create( // Define rules so helper knows which keys to clear under RootKey + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.VAE], + TargetRelativePaths = ["models/vae"], + ConfigDocumentPaths = ["vae"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS], + TargetRelativePaths = ["models/loras"], + ConfigDocumentPaths = ["loras"] + } + ) + }; + + // Act - Write initial content, then run Default Mode + using var stream = new MemoryStream(); + await using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true)) + { + await writer.WriteAsync(initialYamlContent); + } + stream.Position = 0; // Reset for the helper + + await SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync(layout, MockPackageRoot, stream); // Use overload that reads layout options + stream.Position = 0; + using var reader = new StreamReader(stream); + var outputYaml = await reader.ReadToEndAsync(); + + // Assert + var yamlStream = new YamlStream(); + yamlStream.Load(new StringReader(outputYaml)); + var rootMapping = yamlStream.Documents[0].RootNode as YamlMappingNode; + Assert.IsNotNull(rootMapping); + + // Check that stability_matrix key is not gone (or empty) + Assert.IsTrue( + rootMapping.Children.ContainsKey(new YamlScalarNode("stability_matrix")), + "stability_matrix key should exist." + ); + // Check that other keys remain + Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode("some_other_key"))); + Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode("another_key"))); + } + + [TestMethod] + public async Task Yaml_UpdateForDefault_RemovesSmRootKey() + { + // Arrange + var initialYamlContent = """ + # Existing content + some_other_key: value + stability_matrix: + vae: + - C:\SM\Models/VAE + loras: + - C:\SM\Models/Lora + - C:\SM\Models/LyCORIS + another_key: 123 + """; + var layout = new SharedFolderLayout + { + RelativeConfigPath = "extra_paths.yaml", + ConfigFileType = ConfigFileType.Yaml, + ConfigSharingOptions = ConfigSharingOptions.Default with + { + RootKey = "stability_matrix", + ConfigDefaultType = ConfigDefaultType.ClearRoot // Configure clearing of RootKey + }, + Rules = ImmutableList.Create( // Define rules so helper knows which keys to clear under RootKey + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.VAE], + TargetRelativePaths = ["models/vae"], + ConfigDocumentPaths = ["vae"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS], + TargetRelativePaths = ["models/loras"], + ConfigDocumentPaths = ["loras"] + } + ) + }; + + // Act - Write initial content, then run Default Mode + using var stream = new MemoryStream(); + await using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true)) + { + await writer.WriteAsync(initialYamlContent); + } + stream.Position = 0; // Reset for the helper + + await SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync(layout, MockPackageRoot, stream); // Use overload that reads layout options + stream.Position = 0; + using var reader = new StreamReader(stream); + var outputYaml = await reader.ReadToEndAsync(); + + // Assert + var yamlStream = new YamlStream(); + yamlStream.Load(new StringReader(outputYaml)); + var rootMapping = yamlStream.Documents[0].RootNode as YamlMappingNode; + Assert.IsNotNull(rootMapping); + + // Check that stability_matrix key is gone (or empty) + Assert.IsFalse( + rootMapping.Children.ContainsKey(new YamlScalarNode("stability_matrix")), + "stability_matrix key should be removed." + ); + // Check that other keys remain + Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode("some_other_key"))); + Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode("another_key"))); + } + + // --- FDS Tests --- + + [TestMethod] + public async Task Fds_UpdateForShared_WritesCorrectPathsWithRoot() + { + // Arrange + var layout = new SharedFolderLayout + { + RelativeConfigPath = Path.Combine("Data", "Settings.fds"), + ConfigFileType = ConfigFileType.Fds, + ConfigSharingOptions = ConfigSharingOptions.Default, // RootKey not used by FDS strategy directly + Rules = ImmutableList.Create( + new SharedFolderLayoutRule { ConfigDocumentPaths = ["ModelRoot"], IsRoot = true }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.StableDiffusion], + ConfigDocumentPaths = ["SDModelFolder"] + } + ) + }; + var expectedModelRoot = MockSharedModelsRoot.Replace('/', Path.DirectorySeparatorChar); + var expectedSdModelFolder = Path.Combine(MockSharedModelsRoot, "StableDiffusion") + .Replace('/', Path.DirectorySeparatorChar); + + // Act + var outputFds = await RunHelperAndGetOutput( + layout, + MockPackageRoot, + MockSharedModelsRoot, + useSharedMode: true + ); + var fdsSection = new FDSSection(outputFds); + + // Assert + Assert.IsNotNull(fdsSection); + var pathsSection = fdsSection.GetSection("Paths"); + Assert.IsNotNull(pathsSection); + Assert.AreEqual(expectedModelRoot, pathsSection.GetString("ModelRoot")); + Assert.AreEqual(expectedSdModelFolder, pathsSection.GetString("SDModelFolder")); + } + + [TestMethod] + public async Task Fds_UpdateForDefault_WritesCorrectPaths() + { + // Arrange + var layout = new SharedFolderLayout + { + RelativeConfigPath = Path.Combine("Data", "Settings.fds"), + ConfigFileType = ConfigFileType.Fds, + ConfigSharingOptions = ConfigSharingOptions.Default, + Rules = ImmutableList.Create( + // Root rule should result in ModelRoot being *removed* in Default mode + new SharedFolderLayoutRule { ConfigDocumentPaths = ["ModelRoot"], IsRoot = true }, + // Regular rule should write the default path + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.StableDiffusion], + TargetRelativePaths = ["Models/Stable-Diffusion"], + ConfigDocumentPaths = ["SDModelFolder"] + } + ) + }; + var expectedSdModelFolder = Path.Combine(MockPackageRoot, "Models", "Stable-Diffusion") + .Replace('/', Path.DirectorySeparatorChar); + + // Act + var outputFds = await RunHelperAndGetOutput( + layout, + MockPackageRoot, + MockSharedModelsRoot, + useSharedMode: false + ); // Default Mode + var fdsSection = new FDSSection(outputFds); + + // Assert + Assert.IsNotNull(fdsSection); + var pathsSection = fdsSection.GetSection("Paths"); // May or may not exist depending on if SDModelFolder was only key + if (pathsSection != null) + { + Assert.IsNull( + pathsSection.GetString("ModelRoot"), + "ModelRoot should be removed in Default mode." + ); // Check ModelRoot is gone + Assert.AreEqual(expectedSdModelFolder, pathsSection.GetString("SDModelFolder")); + } + else + { + // If only ModelRoot was defined, Paths section itself might be removed, which is also ok + Assert.IsNull( + fdsSection.GetSection("Paths"), + "Paths section should be removed if only ModelRoot existed." + ); + } + } + + [TestMethod] + public async Task Json_SplitRule_UpdateForShared_WritesCorrectArray() + { + // Arrange: Simulate SDFX IP-Adapter rules + var layout = new SharedFolderLayout + { + RelativeConfigPath = "config.json", + ConfigFileType = ConfigFileType.Json, + ConfigSharingOptions = ConfigSharingOptions.Default with { AlwaysWriteArray = true }, // Force array + Rules = ImmutableList.Create( + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.IpAdapter], + ConfigDocumentPaths = ["paths.models.ipadapter"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.IpAdapters15], + ConfigDocumentPaths = ["paths.models.ipadapter"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.IpAdaptersXl], + ConfigDocumentPaths = ["paths.models.ipadapter"] + } + ) + }; + var expectedIpBasePath = Path.Combine(MockSharedModelsRoot, "IpAdapter").Replace('\\', '/'); + var expectedIp15Path = Path.Combine(MockSharedModelsRoot, "IpAdapters15").Replace('\\', '/'); // SM SourceTypes map like this + var expectedIpXlPath = Path.Combine(MockSharedModelsRoot, "IpAdaptersXl").Replace('\\', '/'); + + // Act + var outputJson = await RunHelperAndGetOutput( + layout, + MockPackageRoot, + MockSharedModelsRoot, + useSharedMode: true + ); + var jsonNode = JsonNode.Parse(outputJson); + + // Assert + Assert.IsNotNull(jsonNode); + var ipAdapterNode = jsonNode?["paths"]?["models"]?["ipadapter"]; + Assert.IsInstanceOfType(ipAdapterNode, typeof(JsonArray)); + + var ipAdapterArray = ipAdapterNode as JsonArray; + Assert.AreEqual(3, ipAdapterArray?.Count); + Assert.IsTrue(ipAdapterArray.Any(n => n?.GetValue() == expectedIpBasePath)); + Assert.IsTrue(ipAdapterArray.Any(n => n?.GetValue() == expectedIp15Path)); + Assert.IsTrue(ipAdapterArray.Any(n => n?.GetValue() == expectedIpXlPath)); + } + + // Add more tests: + // - Starting with an existing config file and modifying it. + // - Testing specific ConfigSharingOptions (AlwaysWriteArray for JSON, different RootKey for YAML). + // - Testing removal of keys when rules are removed from the layout. + // - Edge cases like empty layouts or layouts with no matching rules. +} From e24de44d1b5f138c568a4a0187baca27226a3929 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:12:08 -0400 Subject: [PATCH 200/297] Update Fooocus with SharedFolderLayout --- .../Models/Packages/Fooocus.cs | 65 ++----------------- 1 file changed, 4 insertions(+), 61 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs index 4d3fef43..94b5fca6 100644 --- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs @@ -4,6 +4,7 @@ using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Packages.Config; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Python; @@ -17,9 +18,7 @@ public class Fooocus( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper -) - : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper), - ISharedFolderLayoutPackage +) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper) { public override string Name => "Fooocus"; public override string DisplayName { get; set; } = "Fooocus"; @@ -151,13 +150,11 @@ IPrerequisiteHelper prerequisiteHelper public override IEnumerable AvailableSharedFolderMethods => new[] { SharedFolderMethod.Symlink, SharedFolderMethod.Configuration, SharedFolderMethod.None }; - public override Dictionary> SharedFolders => - ((ISharedFolderLayoutPackage)this).LegacySharedFolders; - - public virtual SharedFolderLayout SharedFolderLayout => + public override SharedFolderLayout SharedFolderLayout => new() { RelativeConfigPath = "config.txt", + ConfigFileType = ConfigFileType.Json, Rules = [ new SharedFolderLayoutRule @@ -347,58 +344,4 @@ void HandleConsoleOutput(ProcessOutput s) OnExit ); } - - public override Task SetupModelFolders( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) - { - return sharedFolderMethod switch - { - SharedFolderMethod.Symlink - => base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink), - SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory), - SharedFolderMethod.None => Task.CompletedTask, - _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) - }; - } - - public override Task RemoveModelFolderLinks( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) - { - return sharedFolderMethod switch - { - SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), - SharedFolderMethod.Configuration => WriteDefaultConfig(installDirectory), - SharedFolderMethod.None => Task.CompletedTask, - _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) - }; - } - - private async Task SetupModelFoldersConfig(DirectoryPath installDirectory) - { - // doesn't always exist on first install - installDirectory.JoinDir(OutputFolderName).Create(); - - await SharedFoldersConfigHelper - .UpdateJsonConfigFileForSharedAsync( - SharedFolderLayout, - installDirectory, - SettingsManager.ModelsDirectory - ) - .ConfigureAwait(false); - } - - private Task WriteDefaultConfig(DirectoryPath installDirectory) - { - // doesn't always exist on first install - installDirectory.JoinDir(OutputFolderName).Create(); - - return SharedFoldersConfigHelper.UpdateJsonConfigFileForDefaultAsync( - SharedFolderLayout, - installDirectory - ); - } } From 73d9fc243078b1d2e36819ab4df2e17d170e7e1a Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:14:40 -0400 Subject: [PATCH 201/297] Update Sdfx with SharedFolderLayout --- StabilityMatrix.Core/Models/Packages/Sdfx.cs | 237 ++++++++----------- 1 file changed, 103 insertions(+), 134 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/Sdfx.cs b/StabilityMatrix.Core/Models/Packages/Sdfx.cs index 2cd7909e..7eef80a1 100644 --- a/StabilityMatrix.Core/Models/Packages/Sdfx.cs +++ b/StabilityMatrix.Core/Models/Packages/Sdfx.cs @@ -7,6 +7,7 @@ using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Packages.Config; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Python; @@ -40,26 +41,110 @@ IPrerequisiteHelper prerequisiteHelper public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert; public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration; public override List LaunchOptions => [LaunchOptionDefinition.Extras]; - public override Dictionary> SharedFolders => + + public override SharedFolderLayout SharedFolderLayout => new() { - [SharedFolderType.StableDiffusion] = new[] { "data/models/checkpoints" }, - [SharedFolderType.Diffusers] = new[] { "data/models/diffusers" }, - [SharedFolderType.Lora] = new[] { "data/models/loras" }, - [SharedFolderType.TextEncoders] = new[] { "data/models/clip" }, - [SharedFolderType.ClipVision] = new[] { "data/models/clip_vision" }, - [SharedFolderType.Embeddings] = new[] { "data/models/embeddings" }, - [SharedFolderType.VAE] = new[] { "data/models/vae" }, - [SharedFolderType.ApproxVAE] = new[] { "data/models/vae_approx" }, - [SharedFolderType.ControlNet] = new[] { "data/models/controlnet/ControlNet" }, - [SharedFolderType.GLIGEN] = new[] { "data/models/gligen" }, - [SharedFolderType.ESRGAN] = new[] { "data/models/upscale_models" }, - [SharedFolderType.Hypernetwork] = new[] { "data/models/hypernetworks" }, - [SharedFolderType.IpAdapter] = new[] { "data/models/ipadapter/base" }, - [SharedFolderType.IpAdapters15] = new[] { "data/models/ipadapter/sd15" }, - [SharedFolderType.IpAdaptersXl] = new[] { "data/models/ipadapter/sdxl" }, - [SharedFolderType.T2IAdapter] = new[] { "data/models/controlnet/T2IAdapter" }, - [SharedFolderType.PromptExpansion] = new[] { "data/models/prompt_expansion" } + RelativeConfigPath = "sdfx.config.json", + ConfigFileType = ConfigFileType.Json, + Rules = + [ + // Assuming JSON keys are top-level, adjust ConfigDocumentPaths if nested (e.g., "paths.models.checkpoints") + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.StableDiffusion], + TargetRelativePaths = ["data/models/checkpoints"], + ConfigDocumentPaths = ["path.models.checkpoints"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Diffusers], + TargetRelativePaths = ["data/models/diffusers"], + ConfigDocumentPaths = ["path.models.diffusers"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.VAE], + TargetRelativePaths = ["data/models/vae"], + ConfigDocumentPaths = ["path.models.vae"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS], + TargetRelativePaths = ["data/models/loras"], + ConfigDocumentPaths = ["path.models.loras"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Embeddings], + TargetRelativePaths = ["data/models/embeddings"], + ConfigDocumentPaths = ["path.models.embeddings"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Hypernetwork], + TargetRelativePaths = ["data/models/hypernetworks"], + ConfigDocumentPaths = ["path.models.hypernetworks"] + }, + new SharedFolderLayoutRule + { + SourceTypes = + [ + SharedFolderType.ESRGAN, + SharedFolderType.RealESRGAN, + SharedFolderType.SwinIR + ], + TargetRelativePaths = ["data/models/upscale_models"], + ConfigDocumentPaths = ["path.models.upscale_models"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.TextEncoders], + TargetRelativePaths = ["data/models/clip"], + ConfigDocumentPaths = ["path.models.clip"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ClipVision], + TargetRelativePaths = ["data/models/clip_vision"], + ConfigDocumentPaths = ["path.models.clip_vision"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter], + TargetRelativePaths = ["data/models/controlnet"], + ConfigDocumentPaths = ["path.models.controlnet"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.GLIGEN], + TargetRelativePaths = ["data/models/gligen"], + ConfigDocumentPaths = ["path.models.gligen"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ApproxVAE], + TargetRelativePaths = ["data/models/vae_approx"], + ConfigDocumentPaths = ["path.models.vae_approx"] + }, + new SharedFolderLayoutRule + { + SourceTypes = + [ + SharedFolderType.IpAdapter, + SharedFolderType.IpAdapters15, + SharedFolderType.IpAdaptersXl + ], + TargetRelativePaths = ["data/models/ipadapter"], + ConfigDocumentPaths = ["path.models.ipadapter"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.PromptExpansion], + TargetRelativePaths = ["data/models/prompt_expansion"], + ConfigDocumentPaths = ["path.models.prompt_expansion"] + }, + ] }; public override Dictionary> SharedOutputFolders => new() { [SharedOutputType.Text2Img] = new[] { "data/media/output" } }; @@ -176,120 +261,4 @@ private ImmutableDictionary GetEnvVars(ImmutableDictionary - sharedFolderMethod switch - { - SharedFolderMethod.Symlink - => base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink), - SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory), - SharedFolderMethod.None => Task.CompletedTask, - _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) - }; - - public override Task RemoveModelFolderLinks( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) - { - return sharedFolderMethod switch - { - SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), - SharedFolderMethod.Configuration => RemoveConfigSection(installDirectory), - SharedFolderMethod.None => Task.CompletedTask, - _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) - }; - } - - private async Task SetupModelFoldersConfig(DirectoryPath installDirectory) - { - var configPath = Path.Combine(installDirectory, "sdfx.config.json"); - - if (File.Exists(configPath)) - { - var configText = await File.ReadAllTextAsync(configPath).ConfigureAwait(false); - var config = JsonSerializer.Deserialize(configText) ?? new JsonObject(); - var modelsDir = SettingsManager.ModelsDirectory; - - var models = config.GetOrAddNonNullJsonObject(["paths", "models"]); - - models["checkpoints"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.StableDiffusion.GetStringValue()) - ); - models["vae"] = new JsonArray(Path.Combine(modelsDir, SharedFolderType.VAE.GetStringValue())); - models["loras"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.Lora.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.LyCORIS.GetStringValue()) - ); - models["upscale_models"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.ESRGAN.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.RealESRGAN.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.SwinIR.GetStringValue()) - ); - models["embeddings"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.Embeddings.GetStringValue()) - ); - models["hypernetworks"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.Hypernetwork.GetStringValue()) - ); - models["controlnet"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.ControlNet.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.T2IAdapter.GetStringValue()) - ); - models["clip"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.TextEncoders.GetStringValue()) - ); - models["clip_vision"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.ClipVision.GetStringValue()) - ); - models["diffusers"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.Diffusers.GetStringValue()) - ); - models["gligen"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.GLIGEN.GetStringValue()) - ); - models["vae_approx"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.ApproxVAE.GetStringValue()) - ); - models["ipadapter"] = new JsonArray( - Path.Combine(modelsDir, SharedFolderType.IpAdapter.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.IpAdapters15.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.IpAdaptersXl.GetStringValue()) - ); - - await File.WriteAllTextAsync(configPath, config.ToString()).ConfigureAwait(false); - } - } - - private async Task RemoveConfigSection(DirectoryPath installDirectory) - { - var configPath = Path.Combine(installDirectory, "sdfx.config.json"); - - if (File.Exists(configPath)) - { - var configText = await File.ReadAllTextAsync(configPath).ConfigureAwait(false); - var config = JsonSerializer.Deserialize(configText) ?? new JsonObject(); - - var models = config.GetOrAddNonNullJsonObject(["paths", "models"]); - - models["checkpoints"] = new JsonArray(Path.Combine("data", "models", "checkpoints")); - models["clip"] = new JsonArray(Path.Combine("data", "models", "clip")); - models["clip_vision"] = new JsonArray(Path.Combine("data", "models", "clip_vision")); - models["controlnet"] = new JsonArray(Path.Combine("data", "models", "controlnet")); - models["diffusers"] = new JsonArray(Path.Combine("data", "models", "diffusers")); - models["embeddings"] = new JsonArray(Path.Combine("data", "models", "embeddings")); - models["gligen"] = new JsonArray(Path.Combine("data", "models", "gligen")); - models["ipadapter"] = new JsonArray(Path.Combine("data", "models", "ipadapter")); - models["hypernetworks"] = new JsonArray(Path.Combine("data", "models", "hypernetworks")); - models["loras"] = new JsonArray(Path.Combine("data", "models", "loras")); - models["upscale_models"] = new JsonArray(Path.Combine("data", "models", "upscale_models")); - models["vae"] = new JsonArray(Path.Combine("data", "models", "vae")); - models["vae_approx"] = new JsonArray(Path.Combine("data", "models", "vae_approx")); - - await File.WriteAllTextAsync(configPath, config.ToString()).ConfigureAwait(false); - } - } } From 2c15599c1b2ed19816c372efa79191e222d60413 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:16:02 -0400 Subject: [PATCH 202/297] Refactor FluxGym for SharedFolderLayout --- StabilityMatrix.Core/Models/Packages/FluxGym.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/FluxGym.cs b/StabilityMatrix.Core/Models/Packages/FluxGym.cs index 0b229f5c..c4af5cd8 100644 --- a/StabilityMatrix.Core/Models/Packages/FluxGym.cs +++ b/StabilityMatrix.Core/Models/Packages/FluxGym.cs @@ -17,9 +17,7 @@ public class FluxGym( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper -) - : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper), - ISharedFolderLayoutPackage +) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper) { public override string Name => "FluxGym"; public override string DisplayName { get; set; } = "FluxGym"; @@ -41,10 +39,7 @@ IPrerequisiteHelper prerequisiteHelper public override IEnumerable AvailableSharedFolderMethods => new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None }; - public override Dictionary> SharedFolders => - ((ISharedFolderLayoutPackage)this).LegacySharedFolders; - - public virtual SharedFolderLayout SharedFolderLayout => + public override SharedFolderLayout SharedFolderLayout => new() { Rules = @@ -67,7 +62,7 @@ IPrerequisiteHelper prerequisiteHelper ] }; - public override Dictionary>? SharedOutputFolders { get; } + public override Dictionary>? SharedOutputFolders => null; public override IEnumerable AvailableTorchIndices => new[] { TorchIndex.Cuda }; public override string MainBranch => "main"; public override bool ShouldIgnoreReleases => true; From c23dd3f7cc8c591dd12c3eaa6103b95582dbc766 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:17:30 -0400 Subject: [PATCH 203/297] Update Vlad to SharedFolderLayout --- .../Models/Packages/VladAutomatic.cs | 318 ++++++------------ 1 file changed, 109 insertions(+), 209 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index f0e8e376..0a695f7c 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -10,6 +10,7 @@ using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Packages.Config; using StabilityMatrix.Core.Models.Packages.Extensions; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -54,26 +55,116 @@ IPrerequisiteHelper prerequisiteHelper }; // https://github.com/vladmandic/automatic/blob/master/modules/shared.py#L324 - public override Dictionary> SharedFolders => + public virtual SharedFolderLayout SharedFolderLayout => new() { - [SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" }, - [SharedFolderType.Diffusers] = new[] { "models/Diffusers" }, - [SharedFolderType.VAE] = new[] { "models/VAE" }, - [SharedFolderType.Embeddings] = new[] { "models/embeddings" }, - [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, - [SharedFolderType.Codeformer] = new[] { "models/Codeformer" }, - [SharedFolderType.GFPGAN] = new[] { "models/GFPGAN" }, - [SharedFolderType.BSRGAN] = new[] { "models/BSRGAN" }, - [SharedFolderType.ESRGAN] = new[] { "models/ESRGAN" }, - [SharedFolderType.RealESRGAN] = new[] { "models/RealESRGAN" }, - [SharedFolderType.ScuNET] = new[] { "models/ScuNET" }, - [SharedFolderType.SwinIR] = new[] { "models/SwinIR" }, - [SharedFolderType.LDSR] = new[] { "models/LDSR" }, - [SharedFolderType.TextEncoders] = new[] { "models/CLIP" }, - [SharedFolderType.Lora] = new[] { "models/Lora" }, - [SharedFolderType.LyCORIS] = new[] { "models/LyCORIS" }, - [SharedFolderType.ControlNet] = new[] { "models/ControlNet" } + RelativeConfigPath = "config.json", + ConfigFileType = ConfigFileType.Json, + Rules = + [ + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.StableDiffusion], + TargetRelativePaths = ["models/Stable-diffusion"], + ConfigDocumentPaths = ["ckpt_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Diffusers], + TargetRelativePaths = ["models/Diffusers"], + ConfigDocumentPaths = ["diffusers_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.VAE], + TargetRelativePaths = ["models/VAE"], + ConfigDocumentPaths = ["vae_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Embeddings], + TargetRelativePaths = ["models/embeddings"], + ConfigDocumentPaths = ["embeddings_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Hypernetwork], + TargetRelativePaths = ["models/hypernetworks"], + ConfigDocumentPaths = ["hypernetwork_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Codeformer], + TargetRelativePaths = ["models/Codeformer"], + ConfigDocumentPaths = ["codeformer_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.GFPGAN], + TargetRelativePaths = ["models/GFPGAN"], + ConfigDocumentPaths = ["gfpgan_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.BSRGAN], + TargetRelativePaths = ["models/BSRGAN"], + ConfigDocumentPaths = ["bsrgan_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ESRGAN], + TargetRelativePaths = ["models/ESRGAN"], + ConfigDocumentPaths = ["esrgan_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.RealESRGAN], + TargetRelativePaths = ["models/RealESRGAN"], + ConfigDocumentPaths = ["realesrgan_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ScuNET], + TargetRelativePaths = ["models/ScuNET"], + ConfigDocumentPaths = ["scunet_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.SwinIR], + TargetRelativePaths = ["models/SwinIR"], + ConfigDocumentPaths = ["swinir_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.LDSR], + TargetRelativePaths = ["models/LDSR"], + ConfigDocumentPaths = ["ldsr_models_path"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.TextEncoders], + TargetRelativePaths = ["models/CLIP"], + ConfigDocumentPaths = ["clip_models_path"] + }, // CLIP + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora], + TargetRelativePaths = ["models/Lora"], + ConfigDocumentPaths = ["lora_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.LyCORIS], + TargetRelativePaths = ["models/LyCORIS"], + ConfigDocumentPaths = ["lyco_dir"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter], + TargetRelativePaths = ["models/ControlNet"], + ConfigDocumentPaths = ["control_net_models_path"] + }, // Combined ControlNet/T2I + ] }; public override Dictionary>? SharedOutputFolders => @@ -387,197 +478,6 @@ public override async Task Update( return baseUpdateResult; } - public override Task SetupModelFolders( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) - { - switch (sharedFolderMethod) - { - case SharedFolderMethod.Symlink: - return base.SetupModelFolders(installDirectory, sharedFolderMethod); - case SharedFolderMethod.None: - return Task.CompletedTask; - } - - // Config option - var configJsonPath = installDirectory + "config.json"; - var exists = File.Exists(configJsonPath); - JsonObject? configRoot; - if (exists) - { - var configJson = File.ReadAllText(configJsonPath); - try - { - configRoot = JsonSerializer.Deserialize(configJson) ?? new JsonObject(); - } - catch (JsonException e) - { - Logger.Error(e, "Error setting up Vlad shared model config"); - return Task.CompletedTask; - } - } - else - { - configRoot = new JsonObject(); - } - - configRoot["ckpt_dir"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.StableDiffusion.GetStringValue() - ); - configRoot["diffusers_dir"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.Diffusers.GetStringValue() - ); - configRoot["vae_dir"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.VAE.GetStringValue() - ); - configRoot["lora_dir"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.Lora.GetStringValue() - ); - configRoot["lyco_dir"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.LyCORIS.GetStringValue() - ); - configRoot["embeddings_dir"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.Embeddings.GetStringValue() - ); - configRoot["hypernetwork_dir"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.Hypernetwork.GetStringValue() - ); - configRoot["codeformer_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.Codeformer.GetStringValue() - ); - configRoot["gfpgan_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.GFPGAN.GetStringValue() - ); - configRoot["bsrgan_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.BSRGAN.GetStringValue() - ); - configRoot["esrgan_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.ESRGAN.GetStringValue() - ); - configRoot["realesrgan_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.RealESRGAN.GetStringValue() - ); - configRoot["scunet_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.ScuNET.GetStringValue() - ); - configRoot["swinir_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.SwinIR.GetStringValue() - ); - configRoot["ldsr_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.LDSR.GetStringValue() - ); - configRoot["clip_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.TextEncoders.GetStringValue() - ); - configRoot["control_net_models_path"] = Path.Combine( - SettingsManager.ModelsDirectory, - SharedFolderType.ControlNet.GetStringValue() - ); - - var configJsonStr = JsonSerializer.Serialize( - configRoot, - new JsonSerializerOptions { WriteIndented = true } - ); - File.WriteAllText(configJsonPath, configJsonStr); - - return Task.CompletedTask; - } - - public override Task UpdateModelFolders( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) => - sharedFolderMethod switch - { - SharedFolderMethod.Symlink => base.UpdateModelFolders(installDirectory, sharedFolderMethod), - SharedFolderMethod.None => Task.CompletedTask, - SharedFolderMethod.Configuration => SetupModelFolders(installDirectory, sharedFolderMethod), - _ => Task.CompletedTask - }; - - public override Task RemoveModelFolderLinks( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) => - sharedFolderMethod switch - { - SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), - SharedFolderMethod.None => Task.CompletedTask, - SharedFolderMethod.Configuration => RemoveConfigSettings(installDirectory), - _ => Task.CompletedTask - }; - - private Task RemoveConfigSettings(string installDirectory) - { - var configJsonPath = Path.Combine(installDirectory, "config.json"); - var exists = File.Exists(configJsonPath); - JsonObject? configRoot; - if (exists) - { - var configJson = File.ReadAllText(configJsonPath); - try - { - configRoot = JsonSerializer.Deserialize(configJson); - if (configRoot == null) - { - return Task.CompletedTask; - } - } - catch (JsonException e) - { - Logger.Error(e, "Error removing Vlad shared model config"); - return Task.CompletedTask; - } - } - else - { - return Task.CompletedTask; - } - - configRoot.Remove("ckpt_dir"); - configRoot.Remove("diffusers_dir"); - configRoot.Remove("vae_dir"); - configRoot.Remove("lora_dir"); - configRoot.Remove("lyco_dir"); - configRoot.Remove("embeddings_dir"); - configRoot.Remove("hypernetwork_dir"); - configRoot.Remove("codeformer_models_path"); - configRoot.Remove("gfpgan_models_path"); - configRoot.Remove("bsrgan_models_path"); - configRoot.Remove("esrgan_models_path"); - configRoot.Remove("realesrgan_models_path"); - configRoot.Remove("scunet_models_path"); - configRoot.Remove("swinir_models_path"); - configRoot.Remove("ldsr_models_path"); - configRoot.Remove("clip_models_path"); - configRoot.Remove("control_net_models_path"); - - var configJsonStr = JsonSerializer.Serialize( - configRoot, - new JsonSerializerOptions { WriteIndented = true } - ); - File.WriteAllText(configJsonPath, configJsonStr); - - return Task.CompletedTask; - } - private class VladExtensionManager(VladAutomatic package) : GitPackageExtensionManager(package.PrerequisiteHelper) { From 2dfa303d2c67772722eb5b4b59e62a0a3e737302 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:17:52 -0400 Subject: [PATCH 204/297] Update ComfyUI to SharedFolderLayout --- .../Models/Packages/ComfyUI.cs | 413 ++++++------------ 1 file changed, 137 insertions(+), 276 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index fcca571b..a4ec35e1 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -9,6 +9,7 @@ using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.PackageModification; +using StabilityMatrix.Core.Models.Packages.Config; using StabilityMatrix.Core.Models.Packages.Extensions; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -47,29 +48,144 @@ IPrerequisiteHelper prerequisiteHelper public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration; // https://github.com/comfyanonymous/ComfyUI/blob/master/folder_paths.py#L11 - public override Dictionary> SharedFolders => + public override SharedFolderLayout SharedFolderLayout => new() { - [SharedFolderType.StableDiffusion] = ["models/checkpoints"], - [SharedFolderType.Diffusers] = ["models/diffusers"], - [SharedFolderType.Lora] = ["models/loras"], - [SharedFolderType.TextEncoders] = ["models/clip"], - [SharedFolderType.ClipVision] = ["models/clip_vision"], - [SharedFolderType.Embeddings] = ["models/embeddings"], - [SharedFolderType.VAE] = ["models/vae"], - [SharedFolderType.ApproxVAE] = ["models/vae_approx"], - [SharedFolderType.ControlNet] = ["models/controlnet/ControlNet"], - [SharedFolderType.GLIGEN] = ["models/gligen"], - [SharedFolderType.ESRGAN] = ["models/upscale_models"], - [SharedFolderType.Hypernetwork] = ["models/hypernetworks"], - [SharedFolderType.IpAdapter] = ["models/ipadapter/base"], - [SharedFolderType.IpAdapters15] = ["models/ipadapter/sd15"], - [SharedFolderType.IpAdaptersXl] = ["models/ipadapter/sdxl"], - [SharedFolderType.T2IAdapter] = ["models/controlnet/T2IAdapter"], - [SharedFolderType.PromptExpansion] = ["models/prompt_expansion"], - [SharedFolderType.Ultralytics] = ["models/ultralytics"], - [SharedFolderType.Sams] = ["models/sams"], - [SharedFolderType.DiffusionModels] = ["models/diffusion_models"] + RelativeConfigPath = "extra_model_paths.yaml", + ConfigFileType = ConfigFileType.Yaml, + ConfigSharingOptions = + { + RootKey = "stability_matrix", + ConfigDefaultType = ConfigDefaultType.ClearRoot + }, + Rules = + [ + new SharedFolderLayoutRule // Checkpoints + { + SourceTypes = [SharedFolderType.StableDiffusion], + TargetRelativePaths = ["models/checkpoints"], + ConfigDocumentPaths = ["checkpoints"] + }, + new SharedFolderLayoutRule // Diffusers + { + SourceTypes = [SharedFolderType.Diffusers], + TargetRelativePaths = ["models/diffusers"], + ConfigDocumentPaths = ["diffusers"] + }, + new SharedFolderLayoutRule // Loras + { + SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS], + TargetRelativePaths = ["models/loras"], + ConfigDocumentPaths = ["loras"] + }, + new SharedFolderLayoutRule // CLIP (Text Encoders) + { + SourceTypes = [SharedFolderType.TextEncoders], + TargetRelativePaths = ["models/clip"], + ConfigDocumentPaths = ["clip"] + }, + new SharedFolderLayoutRule // CLIP Vision + { + SourceTypes = [SharedFolderType.ClipVision], + TargetRelativePaths = ["models/clip_vision"], + ConfigDocumentPaths = ["clip_vision"] + }, + new SharedFolderLayoutRule // Embeddings / Textual Inversion + { + SourceTypes = [SharedFolderType.Embeddings], + TargetRelativePaths = ["models/embeddings"], + ConfigDocumentPaths = ["embeddings"] + }, + new SharedFolderLayoutRule // VAE + { + SourceTypes = [SharedFolderType.VAE], + TargetRelativePaths = ["models/vae"], + ConfigDocumentPaths = ["vae"] + }, + new SharedFolderLayoutRule // VAE Approx + { + SourceTypes = [SharedFolderType.ApproxVAE], + TargetRelativePaths = ["models/vae_approx"], + ConfigDocumentPaths = ["vae_approx"] + }, + new SharedFolderLayoutRule // ControlNet / T2IAdapter + { + SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter], + TargetRelativePaths = ["models/controlnet"], + ConfigDocumentPaths = ["controlnet"] + }, + new SharedFolderLayoutRule // GLIGEN + { + SourceTypes = [SharedFolderType.GLIGEN], + TargetRelativePaths = ["models/gligen"], + ConfigDocumentPaths = ["gligen"] + }, + new SharedFolderLayoutRule // Upscalers + { + SourceTypes = + [ + SharedFolderType.ESRGAN, + SharedFolderType.RealESRGAN, + SharedFolderType.SwinIR + ], + TargetRelativePaths = ["models/upscale_models"], + ConfigDocumentPaths = ["upscale_models"] + }, + new SharedFolderLayoutRule // Hypernetworks + { + SourceTypes = [SharedFolderType.Hypernetwork], + TargetRelativePaths = ["models/hypernetworks"], + ConfigDocumentPaths = ["hypernetworks"] + }, + new SharedFolderLayoutRule // IP-Adapter Base, SD1.5, SDXL + { + SourceTypes = + [ + SharedFolderType.IpAdapter, + SharedFolderType.IpAdapters15, + SharedFolderType.IpAdaptersXl + ], + TargetRelativePaths = ["models/ipadapter"], // Single target path + ConfigDocumentPaths = ["ipadapter"] + }, + new SharedFolderLayoutRule // Prompt Expansion + { + SourceTypes = [SharedFolderType.PromptExpansion], + TargetRelativePaths = ["models/prompt_expansion"], + ConfigDocumentPaths = ["prompt_expansion"] + }, + new SharedFolderLayoutRule // Ultralytics + { + SourceTypes = [SharedFolderType.Ultralytics], // Might need specific UltralyticsBbox/Segm if symlinks differ + TargetRelativePaths = ["models/ultralytics"], + ConfigDocumentPaths = ["ultralytics"] + }, + // Config only rules for Ultralytics bbox/segm + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Ultralytics], + SourceSubPath = "bbox", + ConfigDocumentPaths = ["ultralytics_bbox"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Ultralytics], + SourceSubPath = "segm", + ConfigDocumentPaths = ["ultralytics_segm"] + }, + new SharedFolderLayoutRule // SAMs + { + SourceTypes = [SharedFolderType.Sams], + TargetRelativePaths = ["models/sams"], + ConfigDocumentPaths = ["sams"] + }, + new SharedFolderLayoutRule // Diffusion Models / Unet + { + SourceTypes = [SharedFolderType.DiffusionModels], + TargetRelativePaths = ["models/diffusion_models"], + ConfigDocumentPaths = ["diffusion_models"] + }, + ] }; public override Dictionary>? SharedOutputFolders => @@ -345,261 +461,6 @@ void HandleConsoleOutput(ProcessOutput s) } } - public override Task SetupModelFolders( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) => - sharedFolderMethod switch - { - SharedFolderMethod.Symlink - => base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink), - SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory), - SharedFolderMethod.None => Task.CompletedTask, - _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) - }; - - public override Task RemoveModelFolderLinks( - DirectoryPath installDirectory, - SharedFolderMethod sharedFolderMethod - ) - { - return sharedFolderMethod switch - { - SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), - SharedFolderMethod.Configuration => RemoveConfigSection(installDirectory), - SharedFolderMethod.None => Task.CompletedTask, - _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) - }; - } - - private async Task SetupModelFoldersConfig(DirectoryPath installDirectory) - { - var extraPathsYamlPath = installDirectory.JoinFile("extra_model_paths.yaml"); - var modelsDir = SettingsManager.ModelsDirectory; - - if (!extraPathsYamlPath.Exists) - { - Logger.Info("Creating extra_model_paths.yaml"); - extraPathsYamlPath.Create(); - } - - var yaml = await extraPathsYamlPath.ReadAllTextAsync().ConfigureAwait(false); - using var sr = new StringReader(yaml); - var yamlStream = new YamlStream(); - yamlStream.Load(sr); - - if (!yamlStream.Documents.Any()) - { - yamlStream.Documents.Add(new YamlDocument(new YamlMappingNode())); - } - - var root = yamlStream.Documents[0].RootNode; - if (root is not YamlMappingNode mappingNode) - { - throw new Exception("Invalid extra_model_paths.yaml"); - } - // check if we have a child called "stability_matrix" - var stabilityMatrixNode = mappingNode.Children.FirstOrDefault( - c => c.Key.ToString() == "stability_matrix" - ); - - if (stabilityMatrixNode.Key != null) - { - if (stabilityMatrixNode.Value is not YamlMappingNode nodeValue) - return; - - nodeValue.Children["checkpoints"] = Path.Combine( - modelsDir, - SharedFolderType.StableDiffusion.GetStringValue() - ); - nodeValue.Children["vae"] = Path.Combine(modelsDir, SharedFolderType.VAE.GetStringValue()); - nodeValue.Children["loras"] = - $"{Path.Combine(modelsDir, SharedFolderType.Lora.GetStringValue())}\n" - + $"{Path.Combine(modelsDir, SharedFolderType.LyCORIS.GetStringValue())}"; - nodeValue.Children["upscale_models"] = - $"{Path.Combine(modelsDir, SharedFolderType.ESRGAN.GetStringValue())}\n" - + $"{Path.Combine(modelsDir, SharedFolderType.RealESRGAN.GetStringValue())}\n" - + $"{Path.Combine(modelsDir, SharedFolderType.SwinIR.GetStringValue())}"; - nodeValue.Children["embeddings"] = Path.Combine( - modelsDir, - SharedFolderType.Embeddings.GetStringValue() - ); - nodeValue.Children["hypernetworks"] = Path.Combine( - modelsDir, - SharedFolderType.Hypernetwork.GetStringValue() - ); - nodeValue.Children["controlnet"] = string.Join( - '\n', - Path.Combine(modelsDir, SharedFolderType.ControlNet.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.T2IAdapter.GetStringValue()) - ); - nodeValue.Children["clip"] = Path.Combine( - modelsDir, - SharedFolderType.TextEncoders.GetStringValue() - ); - nodeValue.Children["clip_vision"] = Path.Combine( - modelsDir, - SharedFolderType.ClipVision.GetStringValue() - ); - nodeValue.Children["diffusers"] = Path.Combine( - modelsDir, - SharedFolderType.Diffusers.GetStringValue() - ); - nodeValue.Children["gligen"] = Path.Combine(modelsDir, SharedFolderType.GLIGEN.GetStringValue()); - nodeValue.Children["vae_approx"] = Path.Combine( - modelsDir, - SharedFolderType.ApproxVAE.GetStringValue() - ); - nodeValue.Children["ipadapter"] = string.Join( - '\n', - Path.Combine(modelsDir, SharedFolderType.IpAdapter.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.IpAdapters15.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.IpAdaptersXl.GetStringValue()) - ); - nodeValue.Children["prompt_expansion"] = Path.Combine( - modelsDir, - SharedFolderType.PromptExpansion.GetStringValue() - ); - nodeValue.Children["ultralytics"] = Path.Combine( - modelsDir, - SharedFolderType.Ultralytics.GetStringValue() - ); - nodeValue.Children["ultralytics_bbox"] = Path.Combine( - modelsDir, - SharedFolderType.Ultralytics.GetStringValue(), - "bbox" - ); - nodeValue.Children["ultralytics_segm"] = Path.Combine( - modelsDir, - SharedFolderType.Ultralytics.GetStringValue(), - "segm" - ); - nodeValue.Children["sams"] = Path.Combine(modelsDir, SharedFolderType.Sams.GetStringValue()); - nodeValue.Children["diffusion_models"] = Path.Combine( - modelsDir, - SharedFolderType.DiffusionModels.GetStringValue() - ); - } - else - { - stabilityMatrixNode = new KeyValuePair( - new YamlScalarNode("stability_matrix"), - new YamlMappingNode - { - { - "checkpoints", - Path.Combine(modelsDir, SharedFolderType.StableDiffusion.GetStringValue()) - }, - { "vae", Path.Combine(modelsDir, SharedFolderType.VAE.GetStringValue()) }, - { - "loras", - $"{Path.Combine(modelsDir, SharedFolderType.Lora.GetStringValue())}\n{Path.Combine(modelsDir, SharedFolderType.LyCORIS.GetStringValue())}" - }, - { - "upscale_models", - $"{Path.Combine(modelsDir, SharedFolderType.ESRGAN.GetStringValue())}\n{Path.Combine(modelsDir, SharedFolderType.RealESRGAN.GetStringValue())}\n{Path.Combine(modelsDir, SharedFolderType.SwinIR.GetStringValue())}" - }, - { "embeddings", Path.Combine(modelsDir, SharedFolderType.Embeddings.GetStringValue()) }, - { - "hypernetworks", - Path.Combine(modelsDir, SharedFolderType.Hypernetwork.GetStringValue()) - }, - { - "controlnet", - string.Join( - '\n', - Path.Combine(modelsDir, SharedFolderType.ControlNet.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.T2IAdapter.GetStringValue()) - ) - }, - { "clip", Path.Combine(modelsDir, SharedFolderType.TextEncoders.GetStringValue()) }, - { "clip_vision", Path.Combine(modelsDir, SharedFolderType.ClipVision.GetStringValue()) }, - { "diffusers", Path.Combine(modelsDir, SharedFolderType.Diffusers.GetStringValue()) }, - { "gligen", Path.Combine(modelsDir, SharedFolderType.GLIGEN.GetStringValue()) }, - { "vae_approx", Path.Combine(modelsDir, SharedFolderType.ApproxVAE.GetStringValue()) }, - { - "ipadapter", - string.Join( - '\n', - Path.Combine(modelsDir, SharedFolderType.IpAdapter.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.IpAdapters15.GetStringValue()), - Path.Combine(modelsDir, SharedFolderType.IpAdaptersXl.GetStringValue()) - ) - }, - { - "prompt_expansion", - Path.Combine(modelsDir, SharedFolderType.PromptExpansion.GetStringValue()) - }, - { "ultralytics", Path.Combine(modelsDir, SharedFolderType.Ultralytics.GetStringValue()) }, - { - "ultralytics_bbox", - Path.Combine(modelsDir, SharedFolderType.Ultralytics.GetStringValue(), "bbox") - }, - { - "ultralytics_segm", - Path.Combine(modelsDir, SharedFolderType.Ultralytics.GetStringValue(), "segm") - }, - { "sams", Path.Combine(modelsDir, SharedFolderType.Sams.GetStringValue()) }, - { - "diffusion_models", - Path.Combine(modelsDir, SharedFolderType.DiffusionModels.GetStringValue()) - } - } - ); - } - - var newRootNode = new YamlMappingNode(); - foreach (var child in mappingNode.Children.Where(c => c.Key.ToString() != "stability_matrix")) - { - newRootNode.Children.Add(child); - } - - newRootNode.Children.Add(stabilityMatrixNode); - - var serializer = new SerializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .WithDefaultScalarStyle(ScalarStyle.Literal) - .Build(); - - var yamlData = serializer.Serialize(newRootNode); - await extraPathsYamlPath.WriteAllTextAsync(yamlData).ConfigureAwait(false); - } - - private static async Task RemoveConfigSection(DirectoryPath installDirectory) - { - var extraPathsYamlPath = installDirectory.JoinFile("extra_model_paths.yaml"); - - if (!extraPathsYamlPath.Exists) - { - return; - } - - var yaml = await extraPathsYamlPath.ReadAllTextAsync().ConfigureAwait(false); - using var sr = new StringReader(yaml); - var yamlStream = new YamlStream(); - yamlStream.Load(sr); - - if (!yamlStream.Documents.Any()) - { - return; - } - - var root = yamlStream.Documents[0].RootNode; - if (root is not YamlMappingNode mappingNode) - { - return; - } - - mappingNode.Children.Remove("stability_matrix"); - - var serializer = new SerializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .Build(); - var yamlData = serializer.Serialize(mappingNode); - - await extraPathsYamlPath.WriteAllTextAsync(yamlData).ConfigureAwait(false); - } - public override IPackageExtensionManager ExtensionManager => new ComfyExtensionManager(this, settingsManager); From 245a6448ceef864ea4c1767c518c21ffda13f5c9 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:24:07 -0400 Subject: [PATCH 205/297] Update StableSwarm to use SharedFolderLayout --- .../Models/Packages/StableSwarm.cs | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs index 1a4f7988..3942ed51 100644 --- a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs +++ b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs @@ -8,6 +8,7 @@ using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Models.FDS; using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Packages.Config; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Services; @@ -118,16 +119,53 @@ await RebuildDotnetProject(installedPackage.FullPath, csprojName, null) LaunchOptionDefinition.Extras ]; - public override Dictionary> SharedFolders => + public override SharedFolderLayout SharedFolderLayout => new() { - [SharedFolderType.StableDiffusion] = ["Models/Stable-Diffusion"], - [SharedFolderType.Lora] = ["Models/Lora"], - [SharedFolderType.VAE] = ["Models/VAE"], - [SharedFolderType.Embeddings] = ["Models/Embeddings"], - [SharedFolderType.ControlNet] = ["Models/controlnet"], - [SharedFolderType.ClipVision] = ["Models/clip_vision"] + RelativeConfigPath = Path.Combine("Data/Settings.fds"), + ConfigFileType = ConfigFileType.Fds, + Rules = + [ + new SharedFolderLayoutRule { IsRoot = true, ConfigDocumentPaths = ["ModelRoot"], }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.StableDiffusion], + TargetRelativePaths = ["Models/Stable-Diffusion"], + ConfigDocumentPaths = ["SDModelFolder"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS], + TargetRelativePaths = ["Models/Lora"], + ConfigDocumentPaths = ["SDLoraFolder"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.VAE], + TargetRelativePaths = ["Models/VAE"], + ConfigDocumentPaths = ["SDVAEFolder"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.Embeddings], + TargetRelativePaths = ["Models/Embeddings"], + ConfigDocumentPaths = ["SDEmbeddingFolder"] + }, + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter], + TargetRelativePaths = ["Models/controlnet"], + ConfigDocumentPaths = ["SDControlNetsFolder"] + }, // Assuming Swarm maps T2I to ControlNet folder + new SharedFolderLayoutRule + { + SourceTypes = [SharedFolderType.ClipVision], + TargetRelativePaths = ["Models/clip_vision"], + ConfigDocumentPaths = ["SDClipVisionFolder"] + }, + ] }; + public override Dictionary> SharedOutputFolders => new() { [SharedOutputType.Text2Img] = [OutputFolderName] }; public override string MainBranch => "master"; @@ -412,7 +450,7 @@ await prerequisiteHelper return await base.CheckForUpdates(package).ConfigureAwait(false); } - public override Task SetupModelFolders( + /*public override Task SetupModelFolders( DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod ) => @@ -420,7 +458,7 @@ SharedFolderMethod sharedFolderMethod { SharedFolderMethod.Symlink => base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink), - SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory), // TODO + SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory), _ => Task.CompletedTask }; @@ -433,7 +471,7 @@ SharedFolderMethod sharedFolderMethod SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), SharedFolderMethod.Configuration => RemoveModelFoldersConfig(installDirectory), _ => Task.CompletedTask - }; + };*/ public override async Task WaitForShutdown() { From 13dfa4887f11f7a3a32c1998564ab5bf7169629f Mon Sep 17 00:00:00 2001 From: jt Date: Tue, 1 Apr 2025 17:32:52 -0700 Subject: [PATCH 206/297] use the right folder paths for rocmlibs & use robocopy instead of move for folder moves --- .../Helpers/WindowsElevated.cs | 28 +++++++++++- .../Helpers/WindowsPrerequisiteHelper.cs | 43 +++++++++++++------ .../Settings/MainSettingsViewModel.cs | 21 ++++++++- 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs b/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs index 7dbe9cd7..cff5aa90 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs @@ -1,7 +1,6 @@ using System.Diagnostics; -using System.Linq; using System.Runtime.Versioning; -using System.Threading.Tasks; +using StabilityMatrix.Core.Models.FileInterfaces; namespace StabilityMatrix.Avalonia.Helpers; @@ -29,6 +28,31 @@ public static async Task MoveFiles(params (string sourcePath, string target return process.ExitCode; } + /// + /// Move a file or folder from source to target using elevated privileges. + /// + public static async Task Robocopy( + DirectoryPath sourcePath, + DirectoryPath targetPath, + FilePath? targetFile = null + ) + { + var targetStr = targetFile is null ? string.Empty : $" \"{targetFile.Name}\""; + var args = $"\"{sourcePath.FullPath}\" \"{targetPath.FullPath}\"{targetStr} /E /MOVE /IS /IT"; + + using var process = new Process(); + process.StartInfo.FileName = "Robocopy.exe"; + process.StartInfo.Arguments = args; + process.StartInfo.UseShellExecute = true; + process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + process.StartInfo.Verb = "runas"; + + process.Start(); + await process.WaitForExitAsync().ConfigureAwait(false); + + return process.ExitCode; + } + /// /// Set a registry key integer using elevated privileges. /// diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index f40aace7..00cc2f31 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -715,28 +715,43 @@ private async Task PatchHipSdkIfNecessary(IProgress? progress) await ArchiveHelper.Extract7Z(rocmLibsDownloadPath, rocmLibsExtractPath, progress); var hipInstalledPath = new DirectoryPath(HipInstalledPath); - var librarySourceDir = rocmLibsExtractPath.JoinDir("library"); + + var zipFolderName = downloadUrl switch + { + _ when downloadUrl.Contains("gfx1201") => null, + _ when downloadUrl.Contains("gfx1150") => "rocm gfx1150 for hip skd 6.2.4", + _ when downloadUrl.Contains("gfx1103.AMD") + => "rocm gfx1103 AMD 780M phoenix V5.0 for hip skd 6.2.4", + _ when downloadUrl.Contains("gfx1034") => "rocm gfx1034-gfx1035-gfx1036 for hip sdk 6.2.4", + _ when downloadUrl.Contains("gfx1032") => "rocm gfx1032 for hip skd 6.2.4(navi21 logic)", + _ when downloadUrl.Contains("gfx1031") => "rocm gfx1031 for hip skd 6.2.4 (littlewu's logic)", + _ when downloadUrl.Contains("gfx1010") + => "rocm gfx1010-xnack-gfx1011-xnack-gfx1012-xnack- for hip sdk 6.2.4", + _ => null + }; + + var librarySourceDir = rocmLibsExtractPath; + if (!string.IsNullOrWhiteSpace(zipFolderName)) + { + librarySourceDir = librarySourceDir.JoinDir(zipFolderName); + } + + librarySourceDir = librarySourceDir.JoinDir("library"); var libraryDestDir = hipInstalledPath.JoinDir("bin", "rocblas", "library"); + await WindowsElevated.Robocopy(librarySourceDir, libraryDestDir); - List<(string sourcePath, string destPath)> listOfMoves = []; - foreach (var file in librarySourceDir.EnumerateFiles(searchOption: SearchOption.AllDirectories)) + var rocblasSource = rocmLibsExtractPath; + if (!string.IsNullOrWhiteSpace(zipFolderName)) { - var relativePath = file.RelativeTo(librarySourceDir); - var newPath = libraryDestDir.JoinFile(relativePath); - newPath.Directory?.Create(); - listOfMoves.Add((file.FullPath, newPath.FullPath)); + rocblasSource = rocblasSource.JoinDir(zipFolderName); } - var rocblasSource = rocmLibsExtractPath.JoinFile("rocblas.dll"); + var rocblasSourceFile = rocblasSource.JoinFile("rocblas.dll"); var rocblasDest = hipInstalledPath.JoinDir("bin").JoinFile("rocblas.dll"); - if (rocblasSource.Exists) + if (rocblasSourceFile.Exists) { - listOfMoves.Add((rocblasSource.FullPath, rocblasDest.FullPath)); + await WindowsElevated.MoveFiles((rocblasSourceFile.FullPath, rocblasDest.FullPath)); } - - progress?.Report(new ProgressReport(-1, "Patching ROCm for your GPU", isIndeterminate: true)); - - await WindowsElevated.MoveFiles(listOfMoves.ToArray()); } private string? GetDownloadUrlFromGpuName(string name) diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs index f975f28a..74009bc3 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs @@ -1074,6 +1074,24 @@ private async Task DebugWhich() } } + [RelayCommand] + private async Task DebugRobocopy() + { + var textFields = new TextBoxField[] + { + new() { Label = "Source" }, + new() { Label = "Destination" } + }; + + var dialog = DialogHelper.CreateTextEntryDialog("Robocopy", "", textFields); + + if (await dialog.ShowAsync() == ContentDialogResult.Primary) + { + var result = await WindowsElevated.Robocopy(textFields[0].Text, textFields[1].Text); + await DialogHelper.CreateMarkdownDialog(result.ToString()).ShowAsync(); + } + } + #endregion #region Debug Commands @@ -1094,7 +1112,8 @@ private async Task DebugWhich() new CommandItem(DebugNvidiaSmiCommand), new CommandItem(DebugShowGitVersionSelectorDialogCommand), new CommandItem(DebugShowMockGitVersionSelectorDialogCommand), - new CommandItem(DebugWhichCommand) + new CommandItem(DebugWhichCommand), + new CommandItem(DebugRobocopyCommand) ]; [RelayCommand] From 198df08e80a85e33a8dd8cf1326199f49b02f86f Mon Sep 17 00:00:00 2001 From: jt Date: Tue, 1 Apr 2025 17:56:39 -0700 Subject: [PATCH 207/297] Add GGUF support for Wan tabs in Inference --- .../Inference/WanModelCardViewModel.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs index ffdd269c..0f71b2c5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs @@ -11,6 +11,7 @@ using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Api.Comfy.Nodes; +using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; namespace StabilityMatrix.Avalonia.ViewModels.Inference; @@ -97,14 +98,30 @@ public async Task ValidateModel() public void ApplyStep(ModuleApplyStepEventArgs e) { - var modelLoader = e.Nodes.AddTypedNode( - new ComfyNodeBuilder.UNETLoader - { - Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UNETLoader)), - UnetName = SelectedModel?.RelativePath ?? throw new ValidationException("Model not selected"), - WeightDtype = SelectedDType ?? "fp8_e4m3fn_fast" - } - ); + ComfyTypedNodeBase modelLoader; + if (SelectedModel?.RelativePath.EndsWith("gguf", StringComparison.OrdinalIgnoreCase) is true) + { + modelLoader = e.Nodes.AddTypedNode( + new ComfyNodeBuilder.UnetLoaderGGUF + { + Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UnetLoaderGGUF)), + UnetName = + SelectedModel?.RelativePath ?? throw new ValidationException("Model not selected") + } + ); + } + else + { + modelLoader = e.Nodes.AddTypedNode( + new ComfyNodeBuilder.UNETLoader + { + Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UNETLoader)), + UnetName = + SelectedModel?.RelativePath ?? throw new ValidationException("Model not selected"), + WeightDtype = SelectedDType ?? "fp8_e4m3fn_fast" + } + ); + } var modelSamplingSd3 = e.Nodes.AddTypedNode( new ComfyNodeBuilder.ModelSamplingSD3 From 4c6f43b0d4819ea05ac8788fd40fcd32258fc922 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 1 Apr 2025 20:57:45 -0400 Subject: [PATCH 208/297] Fix fsd bom saving --- .../Config/FdsConfigSharingStrategy.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs index cd988e45..6a409564 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs @@ -1,4 +1,6 @@ -using FreneticUtilities.FreneticDataSyntax; +using System.Diagnostics; +using System.Text; +using FreneticUtilities.FreneticDataSyntax; namespace StabilityMatrix.Core.Models.Packages.Config; @@ -40,6 +42,8 @@ public async Task UpdateAndWriteAsync( rootSection = new FDSSection(); } + // Debug.WriteLine($"-- Current Fds --\n\n{rootSection.SaveToString()}"); + UpdateFdsConfig(layout, rootSection, pathsSelector, clearPaths, options); // Reset stream to original position before writing @@ -48,8 +52,12 @@ public async Task UpdateAndWriteAsync( configStream.SetLength(initialPosition + 0); // Save using a StreamWriter to control encoding and leave stream open - await using (var writer = new StreamWriter(configStream, System.Text.Encoding.UTF8, leaveOpen: true)) + // Use BOM-less UTF-8 encoding !! (FSD not UTF-8 BOM compatible) + var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + await using (var writer = new StreamWriter(configStream, encoding, leaveOpen: true)) { + // Debug.WriteLine($"-- Saved Fds: --\n\n{rootSection.SaveToString()}"); + await writer .WriteAsync(rootSection.SaveToString().AsMemory(), cancellationToken) .ConfigureAwait(false); @@ -97,20 +105,20 @@ ConfigSharingOptions options else { // No paths for this rule, remove the key - pathsSection.Remove(configPath); // Assuming Remove method exists + // pathsSection.Remove(configPath); // Assuming Remove method exists } } // Remove any keys in the Paths section that are no longer defined by any rule - foreach (var existingKey in currentKeysInPathsSection) + /*foreach (var existingKey in currentKeysInPathsSection) { if (!allRuleKeys.Contains(existingKey)) { pathsSection.Remove(existingKey); } - } + }*/ - // If the Paths section is not empty, add/update it in the root + /*// If the Paths section is not empty, add/update it in the root if (pathsSection.GetRootKeys().Any()) // Check if the section has content { rootSection.Set("Paths", pathsSection); @@ -119,6 +127,6 @@ ConfigSharingOptions options else // Otherwise, remove the empty Paths section from the root { rootSection.Remove("Paths"); // Assuming Remove method exists for sections too - } + }*/ } } From 5879039f37f3fd14b80113cd682ede28faf7bd69 Mon Sep 17 00:00:00 2001 From: jt Date: Tue, 1 Apr 2025 21:29:45 -0700 Subject: [PATCH 209/297] Add Plasma Noise addon for Inference --- CHANGELOG.md | 3 +- StabilityMatrix.Avalonia/App.axaml | 1 + .../Controls/Inference/PlasmaNoiseCard.axaml | 298 ++++++++++++++++++ .../Inference/PlasmaNoiseCard.axaml.cs | 6 + .../DesignData/DesignData.cs | 3 + .../Extensions/ComfyNodeBuilderExtensions.cs | 131 ++++++++ .../Models/Inference/LatentType.cs | 3 +- .../Models/Inference/NoiseType.cs | 10 + .../ViewModels/Base/LoadableViewModelBase.cs | 2 + .../InferenceTextToImageViewModel.cs | 22 ++ .../Inference/Modules/PlasmaNoiseModule.cs | 24 ++ .../Inference/PlasmaNoiseCardViewModel.cs | 59 ++++ .../Inference/SamplerCardViewModel.cs | 36 ++- .../Api/Comfy/Nodes/ComfyNodeBuilder.cs | 264 ++++++++++++++++ 14 files changed, 856 insertions(+), 6 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Controls/Inference/PlasmaNoiseCard.axaml create mode 100644 StabilityMatrix.Avalonia/Controls/Inference/PlasmaNoiseCard.axaml.cs create mode 100644 StabilityMatrix.Avalonia/Models/Inference/NoiseType.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PlasmaNoiseModule.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/Inference/PlasmaNoiseCardViewModel.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 38bd0493..97dfd3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - This automatically wraps the token/selection in parentheses `()` if it's not already weighted. - It modifies existing weights within parentheses or adds weights if none exist (e.g. `(word:1.1)`). - Handles selection spanning multiple tokens intelligently. +- Added Plasma Noise addon to Inference for text to image workflows ### Changed - Changed the names of some of the shared model folders to better reflect their contents - Improved window state handling @@ -33,7 +34,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Fixed large white boxes appearing when tooltips are visible on macOS/Linux ### Supporters #### Visionaries -- A special shout-out to our fantastic Visionary-tier Patreon supporters: Waterclouds, Corey T, and our newest Visionary, bluepopsicle! Your continued generosity powers the future of Stability Matrix—thank you so much! +- A special shout-out to our fantastic Visionary-tier Patreon supporters: Waterclouds, Corey T, and our newest Visionaries, bluepopsicle and Bob S! Your continued generosity powers the future of Stability Matrix—thank you so much! ## v2.13.4 ### Added diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index ca4a8171..8b3f822e 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -97,6 +97,7 @@ + + \ No newline at end of file diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PlasmaNoiseCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/PlasmaNoiseCard.axaml.cs new file mode 100644 index 00000000..bb713649 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/Inference/PlasmaNoiseCard.axaml.cs @@ -0,0 +1,6 @@ +using Injectio.Attributes; + +namespace StabilityMatrix.Avalonia.Controls; + +[RegisterTransient] +public partial class PlasmaNoiseCard : TemplatedControlBase { } diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index cc4427d2..e5c27533 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -881,6 +881,9 @@ public static UpdateSettingsViewModel UpdateSettingsViewModel public static ImgToVidModelCardViewModel ImgToVidModelCardViewModel => DialogFactory.Get(); + public static PlasmaNoiseCardViewModel PlasmaNoiseCardViewModel => + DialogFactory.Get(); + public static ImageGalleryCardViewModel ImageGalleryCardViewModel => DialogFactory.Get(vm => { diff --git a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs index 1addd8c1..ca5738eb 100644 --- a/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs +++ b/StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs @@ -268,4 +268,135 @@ public static string SetupOutputImage(this ComfyNodeBuilder builder) return previewImage.Name; } + + public static void SetupPlasmaLatentSource( + this ComfyNodeBuilder builder, + int width, + int height, + ulong seed, + NoiseType noiseType, + int valueMin = -1, + int valueMax = -1, + int redMin = -1, + int redMax = -1, + int greenMin = -1, + int greenMax = -1, + int blueMin = -1, + int blueMax = -1, + double turbulence = 2.75d + ) + { + var primaryNodeConnection = noiseType switch + { + NoiseType.Plasma + => builder + .Nodes.AddTypedNode( + new ComfyNodeBuilder.PlasmaNoise + { + Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.PlasmaNoise)), + Height = height, + Width = width, + Seed = seed, + ValueMin = valueMin, + ValueMax = valueMax, + RedMin = redMin, + RedMax = redMax, + GreenMin = greenMin, + GreenMax = greenMax, + BlueMin = blueMin, + BlueMax = blueMax, + Turbulence = turbulence, + } + ) + .Output, + + NoiseType.Random + => builder + .Nodes.AddTypedNode( + new ComfyNodeBuilder.RandNoise + { + Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.RandNoise)), + Height = height, + Width = width, + Seed = seed, + ValueMin = valueMin, + ValueMax = valueMax, + RedMin = redMin, + RedMax = redMax, + GreenMin = greenMin, + GreenMax = greenMax, + BlueMin = blueMin, + BlueMax = blueMax + } + ) + .Output, + + NoiseType.Greyscale + => builder + .Nodes.AddTypedNode( + new ComfyNodeBuilder.GreyNoise + { + Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.GreyNoise)), + Height = height, + Width = width, + Seed = seed, + ValueMin = valueMin, + ValueMax = valueMax, + RedMin = redMin, + RedMax = redMax, + GreenMin = greenMin, + GreenMax = greenMax, + BlueMin = blueMin, + BlueMax = blueMax + } + ) + .Output, + + NoiseType.Brown + => builder + .Nodes.AddTypedNode( + new ComfyNodeBuilder.BrownNoise + { + Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.BrownNoise)), + Height = height, + Width = width, + Seed = seed, + ValueMin = valueMin, + ValueMax = valueMax, + RedMin = redMin, + RedMax = redMax, + GreenMin = greenMin, + GreenMax = greenMax, + BlueMin = blueMin, + BlueMax = blueMax + } + ) + .Output, + + NoiseType.Pink + => builder + .Nodes.AddTypedNode( + new ComfyNodeBuilder.PinkNoise + { + Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.PinkNoise)), + Height = height, + Width = width, + Seed = seed, + ValueMin = valueMin, + ValueMax = valueMax, + RedMin = redMin, + RedMax = redMax, + GreenMin = greenMin, + GreenMax = greenMax, + BlueMin = blueMin, + BlueMax = blueMax + } + ) + .Output, + _ => throw new ArgumentOutOfRangeException(nameof(noiseType), noiseType, null) + }; + + builder.Connections.Primary = primaryNodeConnection; + builder.Connections.PrimarySize = new Size(width, height); + } } diff --git a/StabilityMatrix.Avalonia/Models/Inference/LatentType.cs b/StabilityMatrix.Avalonia/Models/Inference/LatentType.cs index d604e378..2f35f2aa 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/LatentType.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/LatentType.cs @@ -4,5 +4,6 @@ public enum LatentType { Default, Sd3, - Hunyuan + Hunyuan, + Plasma } diff --git a/StabilityMatrix.Avalonia/Models/Inference/NoiseType.cs b/StabilityMatrix.Avalonia/Models/Inference/NoiseType.cs new file mode 100644 index 00000000..e5f4ac48 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/Inference/NoiseType.cs @@ -0,0 +1,10 @@ +namespace StabilityMatrix.Avalonia.Models.Inference; + +public enum NoiseType +{ + Plasma, + Random, + Greyscale, + Pink, + Brown +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs index ba6edd59..33487508 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs @@ -24,6 +24,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; [JsonDerivedType(typeof(FaceDetailerViewModel), FaceDetailerViewModel.ModuleKey)] [JsonDerivedType(typeof(DiscreteModelSamplingCardViewModel), DiscreteModelSamplingCardViewModel.ModuleKey)] [JsonDerivedType(typeof(RescaleCfgCardViewModel), RescaleCfgCardViewModel.ModuleKey)] +[JsonDerivedType(typeof(PlasmaNoiseCardViewModel), PlasmaNoiseCardViewModel.ModuleKey)] [JsonDerivedType(typeof(FreeUModule))] [JsonDerivedType(typeof(HiresFixModule))] [JsonDerivedType(typeof(FluxHiresFixModule))] @@ -37,6 +38,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; [JsonDerivedType(typeof(FluxGuidanceModule))] [JsonDerivedType(typeof(DiscreteModelSamplingModule))] [JsonDerivedType(typeof(RescaleCfgModule))] +[JsonDerivedType(typeof(PlasmaNoiseModule))] public abstract class LoadableViewModelBase : DisposableViewModelBase, IJsonLoadableState { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs index df0d2b72..80a898ac 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs @@ -166,6 +166,7 @@ protected override void BuildPrompt(BuildPromptEventArgs args) var isUnetLoader = ModelCardViewModel.SelectedModelLoader is ModelLoader.Gguf or ModelLoader.Unet; var useSd3Latent = SamplerCardViewModel.ModulesCardViewModel.IsModuleEnabled() || isUnetLoader; + var usePlasmaNoise = SamplerCardViewModel.ModulesCardViewModel.IsModuleEnabled(); if (useSd3Latent) { @@ -177,6 +178,27 @@ protected override void BuildPrompt(BuildPromptEventArgs args) latentType: LatentType.Sd3 ); } + else if (usePlasmaNoise) + { + var plasmaVm = SamplerCardViewModel + .ModulesCardViewModel.GetCard() + .GetCard(); + builder.SetupPlasmaLatentSource( + SamplerCardViewModel.Width, + SamplerCardViewModel.Height, + builder.Connections.Seed, + plasmaVm.SelectedNoiseType, + plasmaVm.ValueMin, + plasmaVm.ValueMax, + plasmaVm.RedMin, + plasmaVm.RedMax, + plasmaVm.GreenMin, + plasmaVm.GreenMax, + plasmaVm.BlueMin, + plasmaVm.BlueMax, + plasmaVm.PlasmaTurbulence + ); + } else { // Setup empty latent diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PlasmaNoiseModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PlasmaNoiseModule.cs new file mode 100644 index 00000000..643fd808 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PlasmaNoiseModule.cs @@ -0,0 +1,24 @@ +using Injectio.Attributes; +using StabilityMatrix.Avalonia.Models.Inference; +using StabilityMatrix.Avalonia.Services; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; + +[ManagedService] +[RegisterTransient] +public class PlasmaNoiseModule : ModuleBase +{ + public PlasmaNoiseModule(ServiceManager vmFactory) + : base(vmFactory) + { + Title = "Plasma Noise"; + AddCards(vmFactory.Get()); + } + + protected override void OnApplyStep(ModuleApplyStepEventArgs e) + { + // handled elsewhere + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PlasmaNoiseCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PlasmaNoiseCardViewModel.cs new file mode 100644 index 00000000..6d5cc904 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PlasmaNoiseCardViewModel.cs @@ -0,0 +1,59 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Injectio.Attributes; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Models.Inference; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.ViewModels.Inference; + +[View(typeof(PlasmaNoiseCard))] +[ManagedService] +[RegisterTransient] +public partial class PlasmaNoiseCardViewModel : LoadableViewModelBase +{ + public const string ModuleKey = "PlasmaNoise"; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ShowPlasmaTurbulence))] + private NoiseType selectedNoiseType = NoiseType.Plasma; + + [ObservableProperty] + private double plasmaTurbulence = 2.75; + + [ObservableProperty] + private int valueMin = -1; + + [ObservableProperty] + private int valueMax = -1; + + [ObservableProperty] + private bool isPerChannelClampingEnabled; + + [ObservableProperty] + private bool isPlasmaSamplerEnabled; + + [ObservableProperty] + private int redMin = -1; + + [ObservableProperty] + private int redMax = -1; + + [ObservableProperty] + private int greenMin = -1; + + [ObservableProperty] + private int greenMax = -1; + + [ObservableProperty] + private int blueMin = -1; + + [ObservableProperty] + private int blueMax = -1; + + [ObservableProperty] + private double plasmaSamplerLatentNoise = 0.05; + + public List NoiseTypes => Enum.GetValues().ToList(); + public bool ShowPlasmaTurbulence => SelectedNoiseType == NoiseType.Plasma; +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs index ddcd62f0..395ed645 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs @@ -1,7 +1,5 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Text.Json.Serialization; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -137,7 +135,8 @@ ServiceManager vmFactory typeof(LayerDiffuseModule), typeof(FluxGuidanceModule), typeof(DiscreteModelSamplingModule), - typeof(RescaleCfgModule) + typeof(RescaleCfgModule), + typeof(PlasmaNoiseModule) ]; }); } @@ -363,6 +362,12 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e) var refinerConditioning = e.Temp.Refiner.Conditioning; var useFluxGuidance = ModulesCardViewModel.IsModuleEnabled(); + var plasmaViewModel = ModulesCardViewModel + .GetCard() + .GetCard(); + var usePlasmaSampler = + ModulesCardViewModel.IsModuleEnabled() + && plasmaViewModel.IsPlasmaSamplerEnabled; if (useFluxGuidance) { @@ -424,6 +429,29 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e) e.Builder.Connections.Primary = sampler.Output1; } + else if (usePlasmaSampler) + { + var sampler = e.Nodes.AddTypedNode( + new ComfyNodeBuilder.PlasmaSampler + { + Name = "PlasmaSampler", + Model = e.Temp.Base.Model!.Unwrap(), + NoiseSeed = e.Builder.Connections.Seed, + SamplerName = primarySampler.Name, + Scheduler = primaryScheduler.Name, + Steps = Steps, + Cfg = useFluxGuidance ? 1.0d : CfgScale, + Positive = conditioning.Positive, + Negative = conditioning.Negative, + LatentImage = primaryLatent, + Denoise = DenoiseStrength, + DistributionType = "rand", + LatentNoise = plasmaViewModel.PlasmaSamplerLatentNoise + } + ); + + e.Builder.Connections.Primary = sampler.Output; + } // Use KSampler if no refiner, otherwise need KSamplerAdvanced else if (e.Builder.Connections.Refiner.Model is null) { diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index 93fc2415..9f55004f 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -770,6 +770,270 @@ public record FaceDetailer : ComfyTypedNodeBase public SegmDetectorNodeConnection? SegmDetectorOpt { get; set; } } + /// + /// Plasma Noise generation node (JDC_Plasma) + /// + [TypedNodeOptions(Name = "JDC_Plasma", RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"])] // Name corrected, Extensions added + public record PlasmaNoise : ComfyTypedNodeBase + { + [Range(128, 8192)] + public required int Width { get; init; } = 512; + + [Range(128, 8192)] + public required int Height { get; init; } = 512; + + [Range(0.5d, 32.0d)] + public required double Turbulence { get; init; } = 2.75; + + [Range(-1, 255)] + public required int ValueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int ValueMax { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMin { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMax { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMin { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMax { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMax { get; init; } = -1; + + [Range(0UL, ulong.MaxValue)] // Match Python's max int size + public required ulong Seed { get; init; } = 0; + } + + /// + /// Random Noise generation node (JDC_RandNoise) + /// + [TypedNodeOptions( + Name = "JDC_RandNoise", + RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + )] // Name corrected, Extensions added + public record RandNoise : ComfyTypedNodeBase + { + [Range(128, 8192)] + public required int Width { get; init; } = 512; + + [Range(128, 8192)] + public required int Height { get; init; } = 512; + + [Range(-1, 255)] + public required int ValueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int ValueMax { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMin { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMax { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMin { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMax { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMax { get; init; } = -1; + + [Range(0UL, ulong.MaxValue)] + public required ulong Seed { get; init; } = 0; + } + + /// + /// Greyscale Noise generation node (JDC_GreyNoise) + /// + [TypedNodeOptions( + Name = "JDC_GreyNoise", + RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + )] // Name corrected, Extensions added + public record GreyNoise : ComfyTypedNodeBase + { + [Range(128, 8192)] + public required int Width { get; init; } = 512; + + [Range(128, 8192)] + public required int Height { get; init; } = 512; + + [Range(-1, 255)] + public required int ValueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int ValueMax { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMin { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMax { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMin { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMax { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMax { get; init; } = -1; + + [Range(0UL, ulong.MaxValue)] + public required ulong Seed { get; init; } = 0; + } + + /// + /// Pink Noise generation node (JDC_PinkNoise) + /// + [TypedNodeOptions( + Name = "JDC_PinkNoise", + RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + )] // Name corrected, Extensions added + public record PinkNoise : ComfyTypedNodeBase + { + [Range(128, 8192)] + public required int Width { get; init; } = 512; + + [Range(128, 8192)] + public required int Height { get; init; } = 512; + + [Range(-1, 255)] + public required int ValueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int ValueMax { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMin { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMax { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMin { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMax { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMax { get; init; } = -1; + + [Range(0UL, ulong.MaxValue)] + public required ulong Seed { get; init; } = 0; + } + + /// + /// Brown Noise generation node (JDC_BrownNoise) + /// + [TypedNodeOptions( + Name = "JDC_BrownNoise", + RequiredExtensions = new[] { "https://github.com/Jordach/comfy-plasma" } + )] // Name corrected, Extensions added + public record BrownNoise : ComfyTypedNodeBase + { + [Range(128, 8192)] + public required int Width { get; init; } = 512; + + [Range(128, 8192)] + public required int Height { get; init; } = 512; + + [Range(-1, 255)] + public required int ValueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int ValueMax { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMin { get; init; } = -1; + + [Range(-1, 255)] + public required int RedMax { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMin { get; init; } = -1; + + [Range(-1, 255)] + public required int GreenMax { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMin { get; init; } = -1; + + [Range(-1, 255)] + public required int BlueMax { get; init; } = -1; + + [Range(0UL, ulong.MaxValue)] + public required ulong Seed { get; init; } = 0; + } + + /// + /// Custom KSampler node using alternative noise distribution (JDC_PlasmaSampler) + /// + [TypedNodeOptions( + Name = "JDC_PlasmaSampler", + RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + )] // Name corrected, Extensions added + public record PlasmaSampler : ComfyTypedNodeBase + { + public required ModelNodeConnection Model { get; init; } + + [Range(0UL, ulong.MaxValue)] + public required ulong NoiseSeed { get; init; } = 0; + + [Range(1, 10000)] + public required int Steps { get; init; } = 20; + + [Range(0.0d, 100.0d)] + public required double Cfg { get; init; } = 7.0; + + [Range(0.0d, 1.0d)] + public required double Denoise { get; init; } = 0.9; // Default from Python code + + [Range(0.0d, 1.0d)] + public required double LatentNoise { get; init; } = 0.05; // Default from Python code + + /// + /// Noise distribution type. Expected values: "default", "rand". + /// Validation should ensure one of these values is passed. + /// + public required string DistributionType { get; init; } = "rand"; + + /// + /// Name of the KSampler sampler (e.g., "euler", "dpmpp_2m_sde"). + /// Should correspond to available samplers in comfy.samplers.KSampler.SAMPLERS. + /// + public required string SamplerName { get; init; } // No default in Python, must be provided + + /// + /// Name of the KSampler scheduler (e.g., "normal", "karras", "sgm_uniform"). + /// Should correspond to available schedulers in comfy.samplers.KSampler.SCHEDULERS. + /// + public required string Scheduler { get; init; } // No default in Python, must be provided + + public required ConditioningNodeConnection Positive { get; init; } + public required ConditioningNodeConnection Negative { get; init; } + public required LatentNodeConnection LatentImage { get; init; } + } + public ImageNodeConnection Lambda_LatentToImage(LatentNodeConnection latent, VAENodeConnection vae) { var name = GetUniqueName("VAEDecode"); From 3b1d3ab391be84f2f2a91a1b779ff07c1c73e393 Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 2 Apr 2025 00:55:48 -0400 Subject: [PATCH 210/297] Fix FDS writing with empty content --- .../Models/Packages/Config/FdsConfigSharingStrategy.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs index 6a409564..6c939659 100644 --- a/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs +++ b/StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Text; +using System.Text.Json.Nodes; using FreneticUtilities.FreneticDataSyntax; namespace StabilityMatrix.Core.Models.Packages.Config; @@ -77,7 +78,13 @@ ConfigSharingOptions options var rulesByConfigPath = layout.GetRulesByConfigPath(); // SwarmUI typically stores paths under a "Paths" section - var pathsSection = rootSection.GetSection("Paths") ?? new FDSSection(); // Get or create "Paths" section + // Get or create the Paths section + var pathsSection = rootSection.GetSection("Paths"); + if (pathsSection is null) + { + pathsSection = new FDSSection(); + rootSection.Set("Paths", pathsSection); // Add Paths section to the root + } // Keep track of keys managed by the layout to remove old ones var allRuleKeys = rulesByConfigPath.Keys.ToHashSet(); From 7eabf5638390ccd918a07f0b6da6d0feb52ea269 Mon Sep 17 00:00:00 2001 From: Ionite Date: Wed, 2 Apr 2025 01:10:21 -0400 Subject: [PATCH 211/297] Add changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38bd0493..bc28d359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,17 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - This automatically wraps the token/selection in parentheses `()` if it's not already weighted. - It modifies existing weights within parentheses or adds weights if none exist (e.g. `(word:1.1)`). - Handles selection spanning multiple tokens intelligently. +- (Internal) Introduced unified strategy pattern (`IConfigSharingStrategy`) to for handling different config file formats (JSON, YAML, FDS). + - Added support for configuring nested paths in JSON and YAML files (e.g. `paths.models.vae`) via dot-notation in `SharedFolderLayoutRule.ConfigDocumentPaths`. + - Packages can now use the `SharedFolderLayout` property to define a `ConfigFileType` and `ConfigSharingOptions` (like `RootKey`), without needing to implement custom configuration logic. ### Changed - Changed the names of some of the shared model folders to better reflect their contents - Improved window state handling - Improved Checkpoint Manager memory usage (thanks to @FireGeek for the profiling assistance!) - Upgraded HIP SDK installs to 6.2.4 for ComfyUI-Zluda and AMDGPU-Forge - (Internal) Upgraded FluentAvalonia to 2.3.0 +- (Internal) Refactored configuration-based shared folder logic: Centralized handling into `SharedFoldersConfigHelper` and format-specific strategies, removing custom file I/O logic from individual package classes for improved consistency and maintainability. + - Migrated packages ComfyUI (incl. Zluda), VladAutomatic (SD.Next), Sdfx, and StableSwarm to use the unified system for configuration and symlink based sharing. ### Fixed - Fixed RTX 5000-series GPU detection in certain cases - Fixed Image Viewer animation loader keeping file handles open, which resolves 2 different issues (OSes are fun): From a41d5c5d7935cd56250f3c68d47b4d7e2b367993 Mon Sep 17 00:00:00 2001 From: jt Date: Thu, 3 Apr 2025 18:16:17 -0700 Subject: [PATCH 212/297] remove unused setting that was crashing in some cases --- StabilityMatrix.Core/Models/Settings/Settings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/StabilityMatrix.Core/Models/Settings/Settings.cs b/StabilityMatrix.Core/Models/Settings/Settings.cs index f4e3aa9e..f8baa05c 100644 --- a/StabilityMatrix.Core/Models/Settings/Settings.cs +++ b/StabilityMatrix.Core/Models/Settings/Settings.cs @@ -83,11 +83,6 @@ public InstalledPackage? PreferredWorkflowPackage public bool IsNavExpanded { get; set; } public bool IsImportAsConnected { get; set; } public bool ShowConnectedModelImages { get; set; } - - [JsonConverter(typeof(JsonStringEnumConverter))] - public SharedFolderType SharedFolderVisibleCategories { get; set; } = - SharedFolderType.StableDiffusion | SharedFolderType.Lora | SharedFolderType.LyCORIS; - public WindowSettings? WindowSettings { get; set; } public ModelSearchOptions? ModelSearchOptions { get; set; } From a228996ae413b2bf5d2d72a1b84b6de716957f89 Mon Sep 17 00:00:00 2001 From: Ionite Date: Fri, 4 Apr 2025 13:11:07 -0400 Subject: [PATCH 213/297] Add override warn as error --- Directory.Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/Directory.Build.props b/Directory.Build.props index bde27a37..2d898637 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,6 +5,7 @@ enable true true + CS0108 From e889a469fad28acb8470f723c8880e38746f3d0d Mon Sep 17 00:00:00 2001 From: jt Date: Fri, 4 Apr 2025 18:29:06 -0700 Subject: [PATCH 214/297] Fix PlasmaNoise addon being required & also enable previews for PlasmaSampler --- .../Inference/SamplerCardViewModel.cs | 22 ++++++++--- .../Api/Comfy/Nodes/ComfyNodeBuilder.cs | 37 ++++++++++--------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs index 395ed645..43dd1252 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs @@ -362,12 +362,18 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e) var refinerConditioning = e.Temp.Refiner.Conditioning; var useFluxGuidance = ModulesCardViewModel.IsModuleEnabled(); - var plasmaViewModel = ModulesCardViewModel - .GetCard() - .GetCard(); - var usePlasmaSampler = - ModulesCardViewModel.IsModuleEnabled() - && plasmaViewModel.IsPlasmaSamplerEnabled; + + var isPlasmaEnabled = ModulesCardViewModel.IsModuleEnabled(); + var usePlasmaSampler = false; + + if (isPlasmaEnabled) + { + var plasmaViewModel = ModulesCardViewModel + .GetCard() + .GetCard(); + + usePlasmaSampler = plasmaViewModel.IsPlasmaSamplerEnabled; + } if (useFluxGuidance) { @@ -431,6 +437,10 @@ private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e) } else if (usePlasmaSampler) { + var plasmaViewModel = ModulesCardViewModel + .GetCard() + .GetCard(); + var sampler = e.Nodes.AddTypedNode( new ComfyNodeBuilder.PlasmaSampler { diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index 9f55004f..98f56b86 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -771,9 +771,12 @@ public record FaceDetailer : ComfyTypedNodeBase } /// - /// Plasma Noise generation node (JDC_Plasma) + /// Plasma Noise generation node (Lykos_JDC_Plasma) /// - [TypedNodeOptions(Name = "JDC_Plasma", RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"])] // Name corrected, Extensions added + [TypedNodeOptions( + Name = "Lykos_JDC_Plasma", + RequiredExtensions = ["https://github.com/LykosAI/inference-comfy-plasma"] + )] // Name corrected, Extensions added public record PlasmaNoise : ComfyTypedNodeBase { [Range(128, 8192)] @@ -814,11 +817,11 @@ public record PlasmaNoise : ComfyTypedNodeBase } /// - /// Random Noise generation node (JDC_RandNoise) + /// Random Noise generation node (Lykos_JDC_RandNoise) /// [TypedNodeOptions( - Name = "JDC_RandNoise", - RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + Name = "Lykos_JDC_RandNoise", + RequiredExtensions = ["https://github.com/LykosAI/inference-comfy-plasma"] )] // Name corrected, Extensions added public record RandNoise : ComfyTypedNodeBase { @@ -857,11 +860,11 @@ public record RandNoise : ComfyTypedNodeBase } /// - /// Greyscale Noise generation node (JDC_GreyNoise) + /// Greyscale Noise generation node (Lykos_JDC_GreyNoise) /// [TypedNodeOptions( - Name = "JDC_GreyNoise", - RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + Name = "Lykos_JDC_GreyNoise", + RequiredExtensions = ["https://github.com/LykosAI/inference-comfy-plasma"] )] // Name corrected, Extensions added public record GreyNoise : ComfyTypedNodeBase { @@ -900,11 +903,11 @@ public record GreyNoise : ComfyTypedNodeBase } /// - /// Pink Noise generation node (JDC_PinkNoise) + /// Pink Noise generation node (Lykos_JDC_PinkNoise) /// [TypedNodeOptions( - Name = "JDC_PinkNoise", - RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + Name = "Lykos_JDC_PinkNoise", + RequiredExtensions = ["https://github.com/LykosAI/inference-comfy-plasma"] )] // Name corrected, Extensions added public record PinkNoise : ComfyTypedNodeBase { @@ -943,11 +946,11 @@ public record PinkNoise : ComfyTypedNodeBase } /// - /// Brown Noise generation node (JDC_BrownNoise) + /// Brown Noise generation node (Lykos_JDC_BrownNoise) /// [TypedNodeOptions( - Name = "JDC_BrownNoise", - RequiredExtensions = new[] { "https://github.com/Jordach/comfy-plasma" } + Name = "Lykos_JDC_BrownNoise", + RequiredExtensions = new[] { "https://github.com/LykosAI/inference-comfy-plasma" } )] // Name corrected, Extensions added public record BrownNoise : ComfyTypedNodeBase { @@ -986,11 +989,11 @@ public record BrownNoise : ComfyTypedNodeBase } /// - /// Custom KSampler node using alternative noise distribution (JDC_PlasmaSampler) + /// Custom KSampler node using alternative noise distribution (Lykos_JDC_PlasmaSampler) /// [TypedNodeOptions( - Name = "JDC_PlasmaSampler", - RequiredExtensions = ["https://github.com/Jordach/comfy-plasma"] + Name = "Lykos_JDC_PlasmaSampler", + RequiredExtensions = ["https://github.com/LykosAI/inference-comfy-plasma"] )] // Name corrected, Extensions added public record PlasmaSampler : ComfyTypedNodeBase { From f8b0b00a98b7bf33e4f0a8e9fe5056cb9df90fa6 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 5 Apr 2025 01:46:46 -0400 Subject: [PATCH 215/297] Fix SharedFolderLayoutRule Union not copying SourceSubPath --- .../Models/Packages/SharedFolderLayoutRule.cs | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs b/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs index dc0d617c..43dd41df 100644 --- a/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs +++ b/StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs @@ -1,12 +1,12 @@ namespace StabilityMatrix.Core.Models.Packages; -public readonly record struct SharedFolderLayoutRule +public readonly record struct SharedFolderLayoutRule() { - public SharedFolderType[] SourceTypes { get; init; } + public SharedFolderType[] SourceTypes { get; init; } = []; - public string[] TargetRelativePaths { get; init; } + public string[] TargetRelativePaths { get; init; } = []; - public string[] ConfigDocumentPaths { get; init; } + public string[] ConfigDocumentPaths { get; init; } = []; /// /// For rules that use the root models folder instead of a specific SharedFolderType @@ -18,30 +18,6 @@ public readonly record struct SharedFolderLayoutRule /// public string? SourceSubPath { get; init; } - public SharedFolderLayoutRule() - { - SourceTypes = []; - TargetRelativePaths = []; - ConfigDocumentPaths = []; - IsRoot = false; - } - - public SharedFolderLayoutRule(SharedFolderType[] types, string[] targets) - { - SourceTypes = types; - TargetRelativePaths = targets; - ConfigDocumentPaths = []; - IsRoot = false; - } - - public SharedFolderLayoutRule(SharedFolderType[] types, string[] targets, string[] configs) - { - SourceTypes = types; - TargetRelativePaths = targets; - ConfigDocumentPaths = configs; - IsRoot = false; - } - public SharedFolderLayoutRule Union(SharedFolderLayoutRule other) { return this with @@ -49,7 +25,8 @@ public SharedFolderLayoutRule Union(SharedFolderLayoutRule other) SourceTypes = SourceTypes.Union(other.SourceTypes).ToArray(), TargetRelativePaths = TargetRelativePaths.Union(other.TargetRelativePaths).ToArray(), ConfigDocumentPaths = ConfigDocumentPaths.Union(other.ConfigDocumentPaths).ToArray(), - IsRoot = IsRoot || other.IsRoot + IsRoot = IsRoot || other.IsRoot, + SourceSubPath = SourceSubPath ?? other.SourceSubPath }; } } From 84c2ddee8f5f52521f99bb56e70daf9ac0933a69 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 5 Apr 2025 01:47:24 -0400 Subject: [PATCH 216/297] Fix SdNext layouts missing override property --- StabilityMatrix.Core/Models/Packages/VladAutomatic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index 0a695f7c..20e1c4a4 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -55,7 +55,7 @@ IPrerequisiteHelper prerequisiteHelper }; // https://github.com/vladmandic/automatic/blob/master/modules/shared.py#L324 - public virtual SharedFolderLayout SharedFolderLayout => + public override SharedFolderLayout SharedFolderLayout => new() { RelativeConfigPath = "config.json", From 550a08ac52448043498afca44c760ac98dafb3b5 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 5 Apr 2025 01:48:43 -0400 Subject: [PATCH 217/297] Fix some overrides --- .../Behaviors/TextEditorCompletionBehavior.cs | 19 +-------- .../Behaviors/TextEditorToolTipBehavior.cs | 11 ----- .../Controls/BetterComboBox.cs | 20 +-------- .../ViewModels/Base/LoadableViewModelBase.cs | 2 +- .../Inference/ExtraNetworkCardViewModel.cs | 2 +- .../Inference/StackViewModelBase.cs | 2 +- .../ViewModels/InstalledWorkflowsViewModel.cs | 41 +++++++++---------- .../ViewModels/LaunchPageViewModel.cs | 13 +++--- .../ViewModels/RunningPackageViewModel.cs | 14 ++++--- 9 files changed, 42 insertions(+), 82 deletions(-) diff --git a/StabilityMatrix.Avalonia/Behaviors/TextEditorCompletionBehavior.cs b/StabilityMatrix.Avalonia/Behaviors/TextEditorCompletionBehavior.cs index 376563c4..9640f176 100644 --- a/StabilityMatrix.Avalonia/Behaviors/TextEditorCompletionBehavior.cs +++ b/StabilityMatrix.Avalonia/Behaviors/TextEditorCompletionBehavior.cs @@ -43,9 +43,7 @@ public ICompletionProvider? CompletionProvider } public static readonly StyledProperty TokenizerProviderProperty = - AvaloniaProperty.Register( - "TokenizerProvider" - ); + AvaloniaProperty.Register("TokenizerProvider"); public ITokenizerProvider? TokenizerProvider { @@ -53,17 +51,6 @@ public ITokenizerProvider? TokenizerProvider set => SetValue(TokenizerProviderProperty, value); } - public static readonly StyledProperty IsEnabledProperty = AvaloniaProperty.Register< - TextEditorCompletionBehavior, - bool - >("IsEnabled", true); - - public bool IsEnabled - { - get => GetValue(IsEnabledProperty); - set => SetValue(IsEnabledProperty, value); - } - protected override void OnAttached() { base.OnAttached(); @@ -264,9 +251,7 @@ private static bool IsCompletionEndChar(char c) // Still not found if (currentToken is null || currentTokenIndex == -1) { - Logger.Info( - $"Could not find token at caret offset {caret} for line {lineText.ToRepr()}" - ); + Logger.Info($"Could not find token at caret offset {caret} for line {lineText.ToRepr()}"); return null; } diff --git a/StabilityMatrix.Avalonia/Behaviors/TextEditorToolTipBehavior.cs b/StabilityMatrix.Avalonia/Behaviors/TextEditorToolTipBehavior.cs index 90c860db..d4d9f385 100644 --- a/StabilityMatrix.Avalonia/Behaviors/TextEditorToolTipBehavior.cs +++ b/StabilityMatrix.Avalonia/Behaviors/TextEditorToolTipBehavior.cs @@ -39,17 +39,6 @@ public ITokenizerProvider? TokenizerProvider set => SetValue(TokenizerProviderProperty, value); } - public static readonly StyledProperty IsEnabledProperty = AvaloniaProperty.Register< - TextEditorCompletionBehavior, - bool - >("IsEnabled", true); - - public bool IsEnabled - { - get => GetValue(IsEnabledProperty); - set => SetValue(IsEnabledProperty, value); - } - protected override void OnAttached() { base.OnAttached(); diff --git a/StabilityMatrix.Avalonia/Controls/BetterComboBox.cs b/StabilityMatrix.Avalonia/Controls/BetterComboBox.cs index b6bbbf37..08126a7a 100644 --- a/StabilityMatrix.Avalonia/Controls/BetterComboBox.cs +++ b/StabilityMatrix.Avalonia/Controls/BetterComboBox.cs @@ -1,14 +1,10 @@ -using System; -using System.Linq; -using System.Reactive.Linq; +using System.Reactive.Linq; using System.Reactive.Subjects; -using System.Threading; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives.PopupPositioning; -using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Media; using Avalonia.Threading; @@ -19,20 +15,6 @@ namespace StabilityMatrix.Avalonia.Controls; public class BetterComboBox : ComboBox { - public static readonly DirectProperty SelectionBoxItemTemplateProperty = - AvaloniaProperty.RegisterDirect( - nameof(SelectionBoxItemTemplate), - v => v.SelectionBoxItemTemplate, - (x, v) => x.SelectionBoxItemTemplate = v - ); - - public IDataTemplate? SelectionBoxItemTemplate - { - get => selectionBoxItemTemplate; - set => SetAndRaise(SelectionBoxItemTemplateProperty, ref selectionBoxItemTemplate, value); - } - - private IDataTemplate? selectionBoxItemTemplate; private readonly Subject inputSubject = new(); private readonly IDisposable subscription; private readonly Popup inputPopup; diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs index 33487508..648b2d4a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs @@ -39,7 +39,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; [JsonDerivedType(typeof(DiscreteModelSamplingModule))] [JsonDerivedType(typeof(RescaleCfgModule))] [JsonDerivedType(typeof(PlasmaNoiseModule))] -public abstract class LoadableViewModelBase : DisposableViewModelBase, IJsonLoadableState +public abstract class LoadableViewModelBase : ViewModelBase, IJsonLoadableState { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs index 5a8ecb5b..83b2f1c3 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs @@ -20,7 +20,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(ExtraNetworkCard))] [ManagedService] [RegisterTransient] -public partial class ExtraNetworkCardViewModel : LoadableViewModelBase +public partial class ExtraNetworkCardViewModel : DisposableLoadableViewModelBase { public const string ModuleKey = "ExtraNetwork"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs index 1ea56b7e..4b710b92 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs @@ -10,7 +10,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; -public abstract class StackViewModelBase : LoadableViewModelBase +public abstract class StackViewModelBase : DisposableLoadableViewModelBase { private readonly ServiceManager vmFactory; diff --git a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs index ad64c629..606b7592 100644 --- a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs @@ -1,23 +1,14 @@ -using System; -using System.IO; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Linq; +using System.Reactive.Linq; using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using System.Reactive.Linq; using AsyncAwaitBestPractices; using Avalonia.Controls; using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using DynamicData; -using DynamicData.Alias; using DynamicData.Binding; using FluentAvalonia.UI.Controls; using Injectio.Attributes; -using KGySoft.CoreLibraries; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Models; @@ -37,7 +28,7 @@ namespace StabilityMatrix.Avalonia.ViewModels; public partial class InstalledWorkflowsViewModel( ISettingsManager settingsManager, INotificationService notificationService -) : TabViewModelBase, IDisposable +) : TabViewModelBase { public override string Header => Resources.TabLabel_InstalledWorkflows; @@ -60,14 +51,16 @@ protected override async Task OnInitialLoadedAsync() .DistinctUntilChanged() .Select(_ => (Func)FilterWorkflows); - workflowsCache - .Connect() - .DeferUntilLoaded() - .Filter(searchPredicate) - .SortBy(x => x.Index) - .Bind(DisplayedWorkflows) - .ObserveOn(SynchronizationContext.Current) - .Subscribe(); + AddDisposable( + workflowsCache + .Connect() + .DeferUntilLoaded() + .Filter(searchPredicate) + .SortBy(x => x.Index) + .Bind(DisplayedWorkflows) + .ObserveOn(SynchronizationContext.Current!) + .Subscribe() + ); if (Design.IsDesignMode) return; @@ -204,9 +197,13 @@ private void OnWorkflowInstalled(object? sender, EventArgs e) LoadInstalledWorkflowsAsync().SafeFireAndForget(); } - public void Dispose() + protected override void Dispose(bool disposing) { - workflowsCache.Dispose(); - EventManager.Instance.WorkflowInstalled -= OnWorkflowInstalled; + if (disposing) + { + EventManager.Instance.WorkflowInstalled -= OnWorkflowInstalled; + } + + base.Dispose(disposing); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs index 850e196f..3ca83a96 100644 --- a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs @@ -591,14 +591,17 @@ private static TaskDialog CreateExitConfirmDialog() return dialog; } - public void Dispose() + protected override void Dispose(bool disposing) { - RunningPackage?.BasePackage.Shutdown(); - RunningPackage = null; + if (disposing) + { + RunningPackage?.BasePackage.Shutdown(); + RunningPackage = null; - Console.Dispose(); + Console.Dispose(); + } - GC.SuppressFinalize(this); + base.Dispose(disposing); } public async ValueTask DisposeAsync() diff --git a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs index e68559ec..aab820d4 100644 --- a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs @@ -187,12 +187,16 @@ private async Task SendToConsole() ConsoleInput = string.Empty; } - public void Dispose() + protected override void Dispose(bool disposing) { - RunningPackage.BasePackage.Shutdown(); - Console.Dispose(); - subscriptions.Dispose(); - GC.SuppressFinalize(this); + if (disposing) + { + RunningPackage.BasePackage.Shutdown(); + Console.Dispose(); + subscriptions.Dispose(); + } + + base.Dispose(disposing); } public async ValueTask DisposeAsync() From 0352fd035f4e3360637dc3ef9dd31f26a8d9dc54 Mon Sep 17 00:00:00 2001 From: jt Date: Sun, 6 Apr 2025 10:50:56 -0700 Subject: [PATCH 218/297] Fixed controlnet preprocessor not using the right resolution & added GPU check for Triton/Sage install & added macOS for Kohya --- .../Inference/ControlNetCardViewModel.cs | 2 +- .../Inference/Modules/ControlNetModule.cs | 4 +- .../Helper/HardwareInfo/GpuInfo.cs | 14 +++++ .../Api/Comfy/Nodes/ComfyNodeBuilder.cs | 2 +- .../Models/Packages/ComfyUI.cs | 2 +- .../Models/Packages/KohyaSs.cs | 57 ++++++++++++++++++- 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs index 53bb8036..2d3aa0c3 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs @@ -121,7 +121,7 @@ preprocessor is null Name = args.Nodes.GetUniqueName("Preprocessor"), Image = image, Preprocessor = preprocessor.ToString(), - Resolution = Width is <= 2048 and > 0 ? Width : 512 + Resolution = Math.Min(Width, Height) } ); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs index 9b7780c7..d2031d08 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs @@ -61,8 +61,8 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e) Name = e.Nodes.GetUniqueName("ControlNet_Preprocessor"), Image = image, Preprocessor = preprocessor.ToString(), - // Use width if valid, else default of 512 - Resolution = card.Width is <= 2048 and > 0 ? card.Width : 512 + // Use lower of width and height for resolution + Resolution = Math.Min(card.Width, card.Height) } ); diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs index d23f848c..adfae7f8 100644 --- a/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs +++ b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs @@ -37,6 +37,20 @@ public bool IsBlackwellGpu() && !Name.Contains("RTX 5000", StringComparison.OrdinalIgnoreCase); } + public bool IsTritonCompatibleGpu() + { + if (Name is null) + return false; + + return IsNvidia + && Name.Contains("RTX", StringComparison.OrdinalIgnoreCase) + && !Name.Contains("RTX 20") + && !Name.Contains("RTX 4000") + && !Name.Contains("RTX 5000") + && !Name.Contains("RTX 6000") + && !Name.Contains("RTX 8000"); + } + public bool IsAmd => Name?.Contains("amd", StringComparison.OrdinalIgnoreCase) ?? false; public bool IsIntel => Name?.Contains("arc", StringComparison.OrdinalIgnoreCase) ?? false; diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs index 98f56b86..a84e4241 100644 --- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs +++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs @@ -603,7 +603,7 @@ public record AIOPreprocessor : ComfyTypedNodeBase public required string Preprocessor { get; init; } - [Range(64, 2048)] + [Range(64, 16384)] public int Resolution { get; init; } = 512; } diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index a4ec35e1..54fe7942 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -308,7 +308,7 @@ IPrerequisiteHelper prerequisiteHelper [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps]; public override List GetExtraCommands() => - Compat.IsWindows + Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsTritonCompatibleGpu() is true ? [ new ExtraPackageCommand diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs index 5ef3f535..2804e89b 100644 --- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs +++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs @@ -141,11 +141,23 @@ await PrerequisiteHelper if (Compat.IsWindows) { - // Install await venvRunner .CustomInstall(["setup/setup_windows.py", "--headless"], onConsoleOutput) .ConfigureAwait(false); } + else if (Compat.IsMacOS) + { + await venvRunner + .CustomInstall( + [ + "setup/setup_linux.py", + "--platform-requirements-file=requirements_macos_arm64.txt", + "--no_run_accelerate" + ], + onConsoleOutput + ) + .ConfigureAwait(false); + } else if (Compat.IsLinux) { await venvRunner @@ -159,6 +171,49 @@ await venvRunner ) .ConfigureAwait(false); } + + var isBlackwell = + SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu(); + + if (isBlackwell) + { + pipArgs = new PipInstallArgs() + .AddArg("--pre") + .WithTorch() + .WithTorchVision() + .WithTorchAudio() + .WithTorchExtraIndex("nightly/cu128") + .AddArg("--force-reinstall"); + + if (installedPackage.PipOverrides != null) + { + pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); + } + + await venvRunner.PipInstall(pipArgs).ConfigureAwait(false); + + pipArgs = new PipInstallArgs() + .AddArg("--pre") + .AddArg("-U") + .AddArg("--no-deps") + .AddArg("xformers"); + + if (installedPackage.PipOverrides != null) + { + pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); + } + + await venvRunner.PipInstall(pipArgs).ConfigureAwait(false); + + pipArgs = new PipInstallArgs().AddArg("-U").AddArg("bitsandbytes"); + + if (installedPackage.PipOverrides != null) + { + pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); + } + + await venvRunner.PipInstall(pipArgs).ConfigureAwait(false); + } } public override async Task RunPackage( From 595a98176055e379ae9d18eb05278d6ccedbebc4 Mon Sep 17 00:00:00 2001 From: jt Date: Sun, 6 Apr 2025 11:17:27 -0700 Subject: [PATCH 219/297] numpy also --- StabilityMatrix.Core/Models/Packages/KohyaSs.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs index 2804e89b..c45e7c27 100644 --- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs +++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs @@ -190,7 +190,7 @@ await venvRunner pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); } - await venvRunner.PipInstall(pipArgs).ConfigureAwait(false); + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); pipArgs = new PipInstallArgs() .AddArg("--pre") @@ -203,7 +203,7 @@ await venvRunner pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); } - await venvRunner.PipInstall(pipArgs).ConfigureAwait(false); + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); pipArgs = new PipInstallArgs().AddArg("-U").AddArg("bitsandbytes"); @@ -212,7 +212,8 @@ await venvRunner pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); } - await venvRunner.PipInstall(pipArgs).ConfigureAwait(false); + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); + await venvRunner.PipInstall("numpy==1.26.4", onConsoleOutput).ConfigureAwait(false); } } From f49a3fe733773a370068b33f16533a62027ef582 Mon Sep 17 00:00:00 2001 From: jt Date: Sun, 6 Apr 2025 11:22:35 -0700 Subject: [PATCH 220/297] comment --- .../ViewModels/Inference/ControlNetCardViewModel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs index 2d3aa0c3..0e37d989 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs @@ -121,6 +121,7 @@ preprocessor is null Name = args.Nodes.GetUniqueName("Preprocessor"), Image = image, Preprocessor = preprocessor.ToString(), + // Comment explaining why we use the lower of width and height Resolution = Math.Min(Width, Height) } ); From c2ea7251583e0649035e97fa13ec6ed8280bd799 Mon Sep 17 00:00:00 2001 From: jt Date: Sun, 6 Apr 2025 11:23:55 -0700 Subject: [PATCH 221/297] moar comment --- .../ViewModels/Inference/ControlNetCardViewModel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs index 0e37d989..d673e3aa 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs @@ -121,7 +121,9 @@ preprocessor is null Name = args.Nodes.GetUniqueName("Preprocessor"), Image = image, Preprocessor = preprocessor.ToString(), - // Comment explaining why we use the lower of width and height + // AIO wants the lower of the two resolutions. who knows why. + // also why can't we put in the low/high thresholds? + // Or any of the other parameters for the other preprocessors? Resolution = Math.Min(Width, Height) } ); From ae525fff630de4c35112ebbf164f8604355168d7 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 7 Apr 2025 18:15:08 -0400 Subject: [PATCH 222/297] Create .aiexclude --- .aiexclude | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .aiexclude diff --git a/.aiexclude b/.aiexclude new file mode 100644 index 00000000..559ee63b --- /dev/null +++ b/.aiexclude @@ -0,0 +1,30 @@ +# Docs +LICENSE +CHANGELOG.md + +# Legacy +StabilityMatrix/ + +# Tests +*.verified.* + +# Misc projects +StabilityMatrix.Native/ +StabilityMatrix.Native.*/ +StabilityMatrix.Avalonia.Diagnostics/ +StabilityMatrix.Avalonia.Diagnostics/ +StabilityMatrix.UITests/ + +# Vendored +Avalonia.Gif/ + +# Configs +*.editorconfig +*.DotSettings + +# Assets +*.svg +StabilityMatrix.Avalonia/Assets/Fonts/ +StabilityMatrix.Avalonia/Assets/linux-x64/ +StabilityMatrix.Avalonia/Assets/macos-arm64/ +StabilityMatrix.Avalonia/Assets/win-x64/ From a7c8e8deb0f35e4cb1ac5ea7a109696e021f8a40 Mon Sep 17 00:00:00 2001 From: jt Date: Mon, 7 Apr 2025 17:41:36 -0700 Subject: [PATCH 223/297] more kohya mac stuff & chagenlog for last PR --- CHANGELOG.md | 9 +++++++++ StabilityMatrix.Core/Models/Packages/KohyaSs.cs | 14 +++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7576ef84..693ad78c 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.14.0-pre.2 +### Added +- Added macOS option for kohya_ss +### Changed +- Updated install for kohya_ss to support RTX 5000-series GPUs +### Fixed +- Fixed Inference ControlNet Preprocessors using incorrect resolution and increased maximum of smallest dimension to 16384 +- Fixed Triton/Sage install option showing for incompatible GPUs + ## v2.14.0-pre.1 ### Added - Added new Package Command (in the 3-dots menu) for installing Triton & SageAttention in ComfyUI diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs index c45e7c27..c5341ce1 100644 --- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs +++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs @@ -29,21 +29,17 @@ IPyRunner runner public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/kohyass/preview.webp"); public override string OutputFolderName => string.Empty; + public override bool IsCompatible => HardwareHelper.HasNvidiaGpu() || Compat.IsMacOS; - public override bool IsCompatible => HardwareHelper.HasNvidiaGpu(); - - public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Cuda; - - public override string Disclaimer => - "Nvidia GPU with at least 8GB VRAM is recommended. May be unstable on Linux."; + public override TorchIndex GetRecommendedTorchVersion() => + Compat.IsMacOS ? TorchIndex.Mps : TorchIndex.Cuda; public override PackageDifficulty InstallerSortOrder => PackageDifficulty.UltraNightmare; public override PackageType PackageType => PackageType.SdTraining; public override bool OfferInOneClickInstaller => false; public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None; - public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda]; - public override IEnumerable AvailableSharedFolderMethods => - new[] { SharedFolderMethod.None }; + public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda, TorchIndex.Mps]; + public override IEnumerable AvailableSharedFolderMethods => [SharedFolderMethod.None]; public override IEnumerable Prerequisites => base.Prerequisites.Concat([PackagePrerequisite.Tkinter]); From 2198b80a8b4dafc71158aedec69451f220b3c2cb Mon Sep 17 00:00:00 2001 From: jt Date: Mon, 7 Apr 2025 19:14:54 -0700 Subject: [PATCH 224/297] kohya mac too hacky, byeeeee --- CHANGELOG.md | 2 -- .../Models/Packages/KohyaSs.cs | 21 +++---------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 693ad78c..844f2fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,6 @@ 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.14.0-pre.2 -### Added -- Added macOS option for kohya_ss ### Changed - Updated install for kohya_ss to support RTX 5000-series GPUs ### Fixed diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs index c5341ce1..09d7018f 100644 --- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs +++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs @@ -26,19 +26,17 @@ IPyRunner runner public override string LicenseType => "Apache-2.0"; public override string LicenseUrl => "https://github.com/bmaltais/kohya_ss/blob/master/LICENSE.md"; public override string LaunchCommand => "kohya_gui.py"; - public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/kohyass/preview.webp"); public override string OutputFolderName => string.Empty; - public override bool IsCompatible => HardwareHelper.HasNvidiaGpu() || Compat.IsMacOS; + public override bool IsCompatible => HardwareHelper.HasNvidiaGpu(); - public override TorchIndex GetRecommendedTorchVersion() => - Compat.IsMacOS ? TorchIndex.Mps : TorchIndex.Cuda; + public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Cuda; public override PackageDifficulty InstallerSortOrder => PackageDifficulty.UltraNightmare; public override PackageType PackageType => PackageType.SdTraining; public override bool OfferInOneClickInstaller => false; public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None; - public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda, TorchIndex.Mps]; + public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda]; public override IEnumerable AvailableSharedFolderMethods => [SharedFolderMethod.None]; public override IEnumerable Prerequisites => base.Prerequisites.Concat([PackagePrerequisite.Tkinter]); @@ -141,19 +139,6 @@ await venvRunner .CustomInstall(["setup/setup_windows.py", "--headless"], onConsoleOutput) .ConfigureAwait(false); } - else if (Compat.IsMacOS) - { - await venvRunner - .CustomInstall( - [ - "setup/setup_linux.py", - "--platform-requirements-file=requirements_macos_arm64.txt", - "--no_run_accelerate" - ], - onConsoleOutput - ) - .ConfigureAwait(false); - } else if (Compat.IsLinux) { await venvRunner From 4f0252f9a89c014b3551569f2c82fd0147889ec3 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 8 Apr 2025 16:38:45 -0400 Subject: [PATCH 225/297] Fix invoke sharing error --- StabilityMatrix.Core/Models/Packages/InvokeAI.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs index 39fa904c..5d7ca686 100644 --- a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs +++ b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs @@ -382,6 +382,11 @@ await SetupInvokeModelSharingConfig(onConsoleOutput, match, s) } } + // Invoke doing shared folders on startup instead + public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) => Task.CompletedTask; + + public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) => Task.CompletedTask; + private async Task SetupInvokeModelSharingConfig( Action? onConsoleOutput, Match match, From 3f562fd3279a8b87d0812e0f7ca287f0bf95a112 Mon Sep 17 00:00:00 2001 From: jt Date: Wed, 9 Apr 2025 22:00:08 -0700 Subject: [PATCH 226/297] Update RunGit to show progress (on windows) and also use prebuilt sage wheels for sageattention install if available --- .../Helpers/UnixPrerequisiteHelper.cs | 3 +- .../Helpers/WindowsPrerequisiteHelper.cs | 13 +--- .../Helper/IPrerequisiteHelper.cs | 68 ++++++++++++------- .../InstallSageAttentionStep.cs | 41 ++++++++++- .../Models/Packages/BaseGitPackage.cs | 11 ++- .../Models/Packages/StableSwarm.cs | 2 +- .../Models/Packages/VladAutomatic.cs | 7 +- 7 files changed, 101 insertions(+), 44 deletions(-) diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs index 781c6c23..f22df230 100644 --- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs @@ -212,8 +212,7 @@ public Task RunGit( return RunGit(args, workingDirectory); } - /// - public async Task RunGit(ProcessArgs args, string? workingDirectory = null) + private async Task RunGit(ProcessArgs args, string? workingDirectory = null) { var command = args.Prepend("git"); diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index 00cc2f31..f6c5a8cb 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -95,7 +95,7 @@ IPyRunner pyRunner public async Task RunGit( ProcessArgs args, - Action? onProcessOutput, + Action? onProcessOutput = null, string? workingDirectory = null ) { @@ -116,15 +116,6 @@ public async Task RunGit( } } - public async Task RunGit(ProcessArgs args, string? workingDirectory = null) - { - var result = await ProcessRunner - .GetProcessResultAsync(GitExePath, args, workingDirectory) - .ConfigureAwait(false); - - result.EnsureSuccessExitCode(); - } - public Task GetGitOutput(ProcessArgs args, string? workingDirectory = null) { return ProcessRunner.GetProcessResultAsync( @@ -428,7 +419,7 @@ public async Task FixGitLongPaths() try { - await RunGit(["config", "--system", "core.longpaths", "true"]); + await RunGit(["config", "--system", "core.longpaths", "true"], null); return true; } catch (Exception e) diff --git a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs index 84a359d7..e7c7e526 100644 --- a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs +++ b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs @@ -26,12 +26,11 @@ public interface IPrerequisiteHelper /// /// Run embedded git with the given arguments. /// - Task RunGit(ProcessArgs args, Action? onProcessOutput, string? workingDirectory = null); - - /// - /// Run embedded git with the given arguments. - /// - Task RunGit(ProcessArgs args, string? workingDirectory = null); + Task RunGit( + ProcessArgs args, + Action? onProcessOutput = null, + string? workingDirectory = null + ); Task GetGitOutput(ProcessArgs args, string? workingDirectory = null); @@ -77,24 +76,35 @@ await GetGitOutput(["rev-parse", "HEAD"], repositoryPath).ConfigureAwait(false) return version; } - async Task CloneGitRepository(string rootDir, string repositoryUrl, GitVersion? version = null) + async Task CloneGitRepository( + string rootDir, + string repositoryUrl, + GitVersion? version = null, + Action? onProcessOutput = null + ) { // Latest if no version is given if (version is null) { - await RunGit(["clone", "--depth", "1", repositoryUrl], rootDir).ConfigureAwait(false); + await RunGit(["clone", "--depth", "1", repositoryUrl], onProcessOutput, rootDir) + .ConfigureAwait(false); } else if (version.Tag is not null) { - await RunGit(["clone", "--depth", "1", version.Tag, repositoryUrl], rootDir) + await RunGit(["clone", "--depth", "1", version.Tag, repositoryUrl], onProcessOutput, rootDir) .ConfigureAwait(false); } else if (version.Branch is not null && version.CommitSha is not null) { - await RunGit(["clone", "--depth", "1", "--branch", version.Branch, repositoryUrl], rootDir) + await RunGit( + ["clone", "--depth", "1", "--branch", version.Branch, repositoryUrl], + onProcessOutput, + rootDir + ) .ConfigureAwait(false); - await RunGit(["checkout", version.CommitSha, "--force"], rootDir).ConfigureAwait(false); + await RunGit(["checkout", version.CommitSha, "--force"], onProcessOutput, rootDir) + .ConfigureAwait(false); } else { @@ -102,45 +112,55 @@ await RunGit(["clone", "--depth", "1", "--branch", version.Branch, repositoryUrl } } - async Task UpdateGitRepository(string repositoryDir, string repositoryUrl, GitVersion version) + async Task UpdateGitRepository( + string repositoryDir, + string repositoryUrl, + GitVersion version, + Action? onProcessOutput = null + ) { if (!Directory.Exists(Path.Combine(repositoryDir, ".git"))) { - await RunGit(["init"], repositoryDir).ConfigureAwait(false); - await RunGit(["remote", "add", "origin", repositoryUrl], repositoryDir).ConfigureAwait(false); + await RunGit(["init"], onProcessOutput, repositoryDir).ConfigureAwait(false); + await RunGit(["remote", "add", "origin", repositoryUrl], onProcessOutput, repositoryDir) + .ConfigureAwait(false); } // Specify Tag if (version.Tag is not null) { - await RunGit(["fetch", "--tags", "--force"], repositoryDir).ConfigureAwait(false); - await RunGit(["checkout", version.Tag, "--force"], repositoryDir).ConfigureAwait(false); + await RunGit(["fetch", "--tags", "--force"], onProcessOutput, repositoryDir) + .ConfigureAwait(false); + await RunGit(["checkout", version.Tag, "--force"], onProcessOutput, repositoryDir) + .ConfigureAwait(false); // Update submodules - await RunGit(["submodule", "update", "--init", "--recursive"], repositoryDir) + await RunGit(["submodule", "update", "--init", "--recursive"], onProcessOutput, repositoryDir) .ConfigureAwait(false); } // Specify Branch + CommitSha else if (version.Branch is not null && version.CommitSha is not null) { - await RunGit(["fetch", "--force"], repositoryDir).ConfigureAwait(false); + await RunGit(["fetch", "--force"], onProcessOutput, repositoryDir).ConfigureAwait(false); - await RunGit(["checkout", version.CommitSha, "--force"], repositoryDir).ConfigureAwait(false); + await RunGit(["checkout", version.CommitSha, "--force"], onProcessOutput, repositoryDir) + .ConfigureAwait(false); // Update submodules - await RunGit(["submodule", "update", "--init", "--recursive"], repositoryDir) + await RunGit(["submodule", "update", "--init", "--recursive"], onProcessOutput, repositoryDir) .ConfigureAwait(false); } // Specify Branch (Use latest commit) else if (version.Branch is not null) { // Fetch - await RunGit(["fetch", "--force"], repositoryDir).ConfigureAwait(false); + await RunGit(["fetch", "--force"], onProcessOutput, repositoryDir).ConfigureAwait(false); // Checkout - await RunGit(["checkout", version.Branch, "--force"], repositoryDir).ConfigureAwait(false); + await RunGit(["checkout", version.Branch, "--force"], onProcessOutput, repositoryDir) + .ConfigureAwait(false); // Pull latest - await RunGit(["pull", "--autostash", "origin", version.Branch], repositoryDir) + await RunGit(["pull", "--autostash", "origin", version.Branch], onProcessOutput, repositoryDir) .ConfigureAwait(false); // Update submodules - await RunGit(["submodule", "update", "--init", "--recursive"], repositoryDir) + await RunGit(["submodule", "update", "--init", "--recursive"], onProcessOutput, repositoryDir) .ConfigureAwait(false); } // Not specified diff --git a/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs b/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs index 769cccc6..3f781126 100644 --- a/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs @@ -88,7 +88,28 @@ public async Task ExecuteAsync(IProgress? progress = null) return env; }); - progress?.Report(new ProgressReport(-1f, message: "Installing Triton", isIndeterminate: true)); + var torchInfo = await venvRunner.PipShow("torch").ConfigureAwait(false); + var sageWheelUrl = string.Empty; + + if (torchInfo == null) + { + sageWheelUrl = string.Empty; + } + else if (torchInfo.Version.Contains("2.5.1") && torchInfo.Version.Contains("cu124")) + { + sageWheelUrl = + "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu124torch2.5.1-cp310-cp310-win_amd64.whl"; + } + else if (torchInfo.Version.Contains("2.6.0") && torchInfo.Version.Contains("cu126")) + { + sageWheelUrl = + "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu126torch2.6.0-cp310-cp310-win_amd64.whl"; + } + else if (torchInfo.Version.Contains("2.7.0") && torchInfo.Version.Contains("cu128")) + { + sageWheelUrl = + "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu128torch2.7.0-cp310-cp310-win_amd64.whl"; + } var pipArgs = new PipInstallArgs(); if (IsBlackwellGpu) @@ -97,6 +118,19 @@ public async Task ExecuteAsync(IProgress? progress = null) } pipArgs = pipArgs.AddArg("triton-windows"); + if (!string.IsNullOrWhiteSpace(sageWheelUrl)) + { + pipArgs = pipArgs.AddArg(sageWheelUrl); + + progress?.Report( + new ProgressReport(-1f, message: "Installing Triton & SageAttention", isIndeterminate: true) + ); + await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false); + return; + } + + progress?.Report(new ProgressReport(-1f, message: "Installing Triton", isIndeterminate: true)); + await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false); venvRunner.UpdateEnvironmentVariables(env => env.SetItem("SETUPTOOLS_USE_DISTUTILS", "setuptools")); @@ -126,7 +160,10 @@ await downloadService new ProgressReport(-1f, message: "Downloading SageAttention", isIndeterminate: true) ); await prerequisiteHelper - .RunGit(["clone", "https://github.com/thu-ml/SageAttention.git", sageDir.ToString()]) + .RunGit( + ["clone", "https://github.com/thu-ml/SageAttention.git", sageDir.ToString()], + progress.AsProcessOutputHandler() + ) .ConfigureAwait(false); } diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index e7349d96..7abc8910 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -1,9 +1,9 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO.Compression; -using System.Text.RegularExpressions; using NLog; using Octokit; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Models.Database; @@ -250,14 +250,19 @@ await PrerequisiteHelper : versionOptions.BranchName ?? MainBranch, GithubUrl, installLocation - } + }, + progress?.AsProcessOutputHandler() ) .ConfigureAwait(false); if (!versionOptions.IsLatest && !string.IsNullOrWhiteSpace(versionOptions.CommitHash)) { await PrerequisiteHelper - .RunGit(new[] { "checkout", versionOptions.CommitHash }, installLocation) + .RunGit( + new[] { "checkout", versionOptions.CommitHash }, + progress?.AsProcessOutputHandler(), + installLocation + ) .ConfigureAwait(false); } diff --git a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs index 3942ed51..a2981bf2 100644 --- a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs +++ b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs @@ -443,7 +443,7 @@ public override async Task CheckForUpdates(InstalledPackage package) if (needsMigrate) { await prerequisiteHelper - .RunGit(["remote", "set-url", "origin", GithubUrl], package.FullPath) + .RunGit(["remote", "set-url", "origin", GithubUrl], workingDirectory: package.FullPath) .ConfigureAwait(false); } diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index 20e1c4a4..6419d289 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -376,13 +376,18 @@ await PrerequisiteHelper "https://github.com/vladmandic/automatic", installDir.Name }, + progress?.AsProcessOutputHandler(), installDir.Parent?.FullPath ?? "" ) .ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(versionOptions.CommitHash) && !versionOptions.IsLatest) { await PrerequisiteHelper - .RunGit(new[] { "checkout", versionOptions.CommitHash }, installLocation) + .RunGit( + new[] { "checkout", versionOptions.CommitHash }, + progress?.AsProcessOutputHandler(), + installLocation + ) .ConfigureAwait(false); } } From 9359fa119575b0957d20182e56ac413eaa73c815 Mon Sep 17 00:00:00 2001 From: jt Date: Wed, 9 Apr 2025 22:02:18 -0700 Subject: [PATCH 227/297] also progress for these --- .../Extensions/GitPackageExtensionManager.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs index 3cc5e17b..cfbb1f40 100644 --- a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs +++ b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs @@ -258,7 +258,12 @@ public virtual async Task InstallExtensionAsync( ); await prerequisiteHelper - .CloneGitRepository(cloneRoot, repositoryUri.ToString(), version) + .CloneGitRepository( + cloneRoot, + repositoryUri.ToString(), + version, + progress.AsProcessOutputHandler() + ) .ConfigureAwait(false); progress?.Report(new ProgressReport(1f, message: $"Cloned {repositoryUri}")); @@ -307,7 +312,12 @@ public virtual async Task UpdateExtensionAsync( } await prerequisiteHelper - .UpdateGitRepository(repoPath, remoteUrlResult.StandardOutput!.Trim(), version) + .UpdateGitRepository( + repoPath, + remoteUrlResult.StandardOutput!.Trim(), + version, + progress.AsProcessOutputHandler() + ) .ConfigureAwait(false); progress?.Report(new ProgressReport(1f, message: $"Updated git repository {repoPath.Name}")); From 104f9824a1bf8ab6cc0006574b9efb46aad6e75e Mon Sep 17 00:00:00 2001 From: jt Date: Wed, 9 Apr 2025 22:05:20 -0700 Subject: [PATCH 228/297] remove unnecessary null --- StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index f6c5a8cb..cc660414 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -419,7 +419,7 @@ public async Task FixGitLongPaths() try { - await RunGit(["config", "--system", "core.longpaths", "true"], null); + await RunGit(["config", "--system", "core.longpaths", "true"]); return true; } catch (Exception e) From 9be407fc1e7b324ce155f251d2496f496562b1b5 Mon Sep 17 00:00:00 2001 From: jt Date: Fri, 11 Apr 2025 21:51:43 -0700 Subject: [PATCH 229/297] Give extra python libs for triton/sage/etc with every windows venv & fix pip install requirements parsing when deps have spaces around specifiers --- CHANGELOG.md | 1 + .../Helpers/UnixPrerequisiteHelper.cs | 10 ++ .../Helpers/WindowsPrerequisiteHelper.cs | 38 +++++ .../Helper/IPrerequisiteHelper.cs | 2 + .../InstallSageAttentionStep.cs | 148 ++++++++++-------- .../Models/Packages/BaseGitPackage.cs | 12 ++ StabilityMatrix.Core/Python/PipInstallArgs.cs | 31 +++- 7 files changed, 179 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844f2fb2..23ed52a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ### Fixed - Fixed Inference ControlNet Preprocessors using incorrect resolution and increased maximum of smallest dimension to 16384 - Fixed Triton/Sage install option showing for incompatible GPUs +- Fixed errors from invalid pip specifiers in requirements files ## v2.14.0-pre.1 ### Added diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs index f22df230..f88c122b 100644 --- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs @@ -491,4 +491,14 @@ public Task FixGitLongPaths() { throw new PlatformNotSupportedException(); } + + [UnsupportedOSPlatform("Linux")] + [UnsupportedOSPlatform("macOS")] + public Task AddMissingLibsToVenv( + DirectoryPath installedPackagePath, + IProgress? progress = null + ) + { + throw new PlatformNotSupportedException(); + } } diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index cc660414..a36333e3 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -44,6 +44,7 @@ IPyRunner pyRunner private const string HipSdkDownloadUrl = "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-24.Q4-Win10-Win11-For-HIP.exe"; + private const string PythonLibsDownloadUrl = "https://cdn.lykos.ai/python_libs_for_sage.zip"; private string HomeDir => settingsManager.LibraryDir; @@ -633,6 +634,43 @@ public async Task RunDotnet( ); } + [SupportedOSPlatform("Windows")] + public async Task AddMissingLibsToVenv( + DirectoryPath installedPackagePath, + IProgress? progress = null + ) + { + var venvLibsDir = installedPackagePath.JoinDir("venv", "libs"); + var venvIncludeDir = installedPackagePath.JoinDir("venv", "include"); + if ( + venvLibsDir.Exists + && venvIncludeDir.Exists + && venvLibsDir.JoinFile("python3.lib").Exists + && venvLibsDir.JoinFile("python310.lib").Exists + ) + { + Logger.Debug("Python libs already installed at {VenvLibsDir}", venvLibsDir); + return; + } + + var downloadPath = installedPackagePath.JoinFile("python_libs_for_sage.zip"); + var venvDir = installedPackagePath.JoinDir("venv"); + await downloadService + .DownloadToFileAsync(PythonLibsDownloadUrl, downloadPath, progress) + .ConfigureAwait(false); + + progress?.Report( + new ProgressReport(-1f, message: "Extracting Python libraries", isIndeterminate: true) + ); + await ArchiveHelper.Extract7Z(downloadPath, venvDir, progress); + + var includeFolder = venvDir.JoinDir("include"); + var scriptsIncludeFolder = venvDir.JoinDir("Scripts").JoinDir("include"); + await includeFolder.CopyToAsync(scriptsIncludeFolder); + + await downloadPath.DeleteAsync(); + } + private async Task DownloadAndExtractPrerequisite( IProgress? progress, string downloadUrl, diff --git a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs index e7c7e526..3b5aa731 100644 --- a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs +++ b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Runtime.Versioning; using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -203,4 +204,5 @@ Task RunDotnet( ); Task FixGitLongPaths(); + Task AddMissingLibsToVenv(DirectoryPath installedPackagePath, IProgress? progress = null); } diff --git a/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs b/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs index 3f781126..b3fda064 100644 --- a/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs @@ -30,6 +30,56 @@ public async Task ExecuteAsync(IProgress? progress = null) ); } + var venvDir = WorkingDirectory.JoinDir("venv"); + + await using var venvRunner = PyBaseInstall.Default.CreateVenvRunner( + venvDir, + workingDirectory: WorkingDirectory, + environmentVariables: EnvironmentVariables + ); + + var torchInfo = await venvRunner.PipShow("torch").ConfigureAwait(false); + var sageWheelUrl = string.Empty; + + if (torchInfo == null) + { + sageWheelUrl = string.Empty; + } + else if (torchInfo.Version.Contains("2.5.1") && torchInfo.Version.Contains("cu124")) + { + sageWheelUrl = + "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu124torch2.5.1-cp310-cp310-win_amd64.whl"; + } + else if (torchInfo.Version.Contains("2.6.0") && torchInfo.Version.Contains("cu126")) + { + sageWheelUrl = + "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu126torch2.6.0-cp310-cp310-win_amd64.whl"; + } + else if (torchInfo.Version.Contains("2.7.0") && torchInfo.Version.Contains("cu128")) + { + sageWheelUrl = + "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu128torch2.7.0-cp310-cp310-win_amd64.whl"; + } + + var pipArgs = new PipInstallArgs(); + if (IsBlackwellGpu) + { + pipArgs = pipArgs.AddArg("--pre"); + } + pipArgs = pipArgs.AddArg("triton-windows"); + + if (!string.IsNullOrWhiteSpace(sageWheelUrl)) + { + pipArgs = pipArgs.AddArg(sageWheelUrl); + + progress?.Report( + new ProgressReport(-1f, message: "Installing Triton & SageAttention", isIndeterminate: true) + ); + await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false); + return; + } + + // no wheels, gotta build if (!prerequisiteHelper.IsVcBuildToolsInstalled) { throw new MissingPrerequisiteException( @@ -63,14 +113,6 @@ public async Task ExecuteAsync(IProgress? progress = null) : cuda126ExpectedPath.JoinFile("nvcc.exe").ToString(); } - var venvDir = WorkingDirectory.JoinDir("venv"); - - await using var venvRunner = PyBaseInstall.Default.CreateVenvRunner( - venvDir, - workingDirectory: WorkingDirectory, - environmentVariables: EnvironmentVariables - ); - venvRunner.UpdateEnvironmentVariables(env => { var cudaBinPath = Path.GetDirectoryName(nvccPath)!; @@ -88,47 +130,6 @@ public async Task ExecuteAsync(IProgress? progress = null) return env; }); - var torchInfo = await venvRunner.PipShow("torch").ConfigureAwait(false); - var sageWheelUrl = string.Empty; - - if (torchInfo == null) - { - sageWheelUrl = string.Empty; - } - else if (torchInfo.Version.Contains("2.5.1") && torchInfo.Version.Contains("cu124")) - { - sageWheelUrl = - "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu124torch2.5.1-cp310-cp310-win_amd64.whl"; - } - else if (torchInfo.Version.Contains("2.6.0") && torchInfo.Version.Contains("cu126")) - { - sageWheelUrl = - "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu126torch2.6.0-cp310-cp310-win_amd64.whl"; - } - else if (torchInfo.Version.Contains("2.7.0") && torchInfo.Version.Contains("cu128")) - { - sageWheelUrl = - "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu128torch2.7.0-cp310-cp310-win_amd64.whl"; - } - - var pipArgs = new PipInstallArgs(); - if (IsBlackwellGpu) - { - pipArgs = pipArgs.AddArg("--pre"); - } - pipArgs = pipArgs.AddArg("triton-windows"); - - if (!string.IsNullOrWhiteSpace(sageWheelUrl)) - { - pipArgs = pipArgs.AddArg(sageWheelUrl); - - progress?.Report( - new ProgressReport(-1f, message: "Installing Triton & SageAttention", isIndeterminate: true) - ); - await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false); - return; - } - progress?.Report(new ProgressReport(-1f, message: "Installing Triton", isIndeterminate: true)); await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false); @@ -138,19 +139,7 @@ public async Task ExecuteAsync(IProgress? progress = null) progress?.Report( new ProgressReport(-1f, message: "Downloading Python libraries", isIndeterminate: true) ); - var downloadPath = WorkingDirectory.JoinFile("python_libs_for_sage.zip"); - await downloadService - .DownloadToFileAsync(PythonLibsDownloadUrl, downloadPath, progress) - .ConfigureAwait(false); - - progress?.Report( - new ProgressReport(-1f, message: "Extracting Python libraries", isIndeterminate: true) - ); - await ArchiveHelper.Extract7Z(downloadPath, venvDir, progress).ConfigureAwait(false); - - var includeFolder = venvDir.JoinDir("include"); - var scriptsIncludeFolder = venvDir.JoinDir("Scripts").JoinDir("include"); - await includeFolder.CopyToAsync(scriptsIncludeFolder).ConfigureAwait(false); + await AddMissingLibsToVenv(WorkingDirectory, progress).ConfigureAwait(false); var sageDir = WorkingDirectory.JoinDir("SageAttention"); @@ -176,5 +165,40 @@ await venvRunner .ConfigureAwait(false); } + private async Task AddMissingLibsToVenv( + DirectoryPath installedPackagePath, + IProgress? progress = null + ) + { + var venvLibsDir = installedPackagePath.JoinDir("venv", "libs"); + var venvIncludeDir = installedPackagePath.JoinDir("venv", "include"); + if ( + venvLibsDir.Exists + && venvIncludeDir.Exists + && venvLibsDir.JoinFile("python3.lib").Exists + && venvLibsDir.JoinFile("python310.lib").Exists + ) + { + return; + } + + var downloadPath = installedPackagePath.JoinFile("python_libs_for_sage.zip"); + var venvDir = installedPackagePath.JoinDir("venv"); + await downloadService + .DownloadToFileAsync(PythonLibsDownloadUrl, downloadPath, progress) + .ConfigureAwait(false); + + progress?.Report( + new ProgressReport(-1f, message: "Extracting Python libraries", isIndeterminate: true) + ); + await ArchiveHelper.Extract7Z(downloadPath, venvDir, progress).ConfigureAwait(false); + + var includeFolder = venvDir.JoinDir("include"); + var scriptsIncludeFolder = venvDir.JoinDir("Scripts").JoinDir("include"); + await includeFolder.CopyToAsync(scriptsIncludeFolder).ConfigureAwait(false); + + await downloadPath.DeleteAsync().ConfigureAwait(false); + } + public string ProgressTitle => "Installing Triton and SageAttention"; } diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index 7abc8910..e0acbbe1 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -210,6 +210,18 @@ public async Task SetupVenvPure( await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false); } + if (!Compat.IsWindows) + return venvRunner; + + try + { + await PrerequisiteHelper.AddMissingLibsToVenv(installedPackagePath).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.Warn(e, "Failed to add missing libs to venv"); + } + return venvRunner; } diff --git a/StabilityMatrix.Core/Python/PipInstallArgs.cs b/StabilityMatrix.Core/Python/PipInstallArgs.cs index 915d7ede..128aea59 100644 --- a/StabilityMatrix.Core/Python/PipInstallArgs.cs +++ b/StabilityMatrix.Core/Python/PipInstallArgs.cs @@ -44,7 +44,8 @@ public PipInstallArgs WithParsedFromRequirementsTxt( .Where(s => !s.StartsWith('#')) .Select(s => s.Contains('#') ? s.Substring(0, s.IndexOf('#')) : s) .Select(s => s.Trim()) - .Where(s => !string.IsNullOrWhiteSpace(s)); + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(NormalizePackageSpecifier); if (excludePattern is not null) { @@ -56,6 +57,34 @@ public PipInstallArgs WithParsedFromRequirementsTxt( return this.AddArgs(requirementsEntries.Select(Argument.Quoted).ToArray()); } + /// + /// Normalizes a package specifier by removing spaces around version constraint operators. + /// + /// The package specifier to normalize. + /// The normalized package specifier. + private static string NormalizePackageSpecifier(string specifier) + { + // Skip normalization for special pip commands that start with a hyphen + if (specifier.StartsWith('-')) + return specifier; + + // Regex to match common version constraint patterns with spaces + // Matches: package >= 1.0.0, package <= 1.0.0, package == 1.0.0, etc. + var versionConstraintPattern = new Regex(@"^([a-zA-Z0-9\-_.]+)\s*(>=|<=|==|>|<|!=|~=)\s*(.+)$"); + + var match = versionConstraintPattern.Match(specifier); + if (match.Success) + { + var packageName = match.Groups[1].Value; + var versionOperator = match.Groups[2].Value; + var version = match.Groups[3].Value; + + return $"{packageName}{versionOperator}{version}"; + } + + return specifier; + } + public PipInstallArgs WithUserOverrides(List overrides) { var newArgs = this; From 5b5b714694dc09247588071a2087f7e2fbe6b300 Mon Sep 17 00:00:00 2001 From: jt Date: Fri, 11 Apr 2025 21:58:25 -0700 Subject: [PATCH 230/297] compile the regex --- StabilityMatrix.Core/Python/PipInstallArgs.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Core/Python/PipInstallArgs.cs b/StabilityMatrix.Core/Python/PipInstallArgs.cs index 128aea59..42041016 100644 --- a/StabilityMatrix.Core/Python/PipInstallArgs.cs +++ b/StabilityMatrix.Core/Python/PipInstallArgs.cs @@ -8,7 +8,7 @@ namespace StabilityMatrix.Core.Python; [SuppressMessage("ReSharper", "StringLiteralTypo")] -public record PipInstallArgs : ProcessArgsBuilder +public partial record PipInstallArgs : ProcessArgsBuilder { public PipInstallArgs(params Argument[] arguments) : base(arguments) { } @@ -70,7 +70,7 @@ private static string NormalizePackageSpecifier(string specifier) // Regex to match common version constraint patterns with spaces // Matches: package >= 1.0.0, package <= 1.0.0, package == 1.0.0, etc. - var versionConstraintPattern = new Regex(@"^([a-zA-Z0-9\-_.]+)\s*(>=|<=|==|>|<|!=|~=)\s*(.+)$"); + var versionConstraintPattern = PackageSpecifierRegex(); var match = versionConstraintPattern.Match(specifier); if (match.Success) @@ -136,4 +136,7 @@ public override string ToString() { return base.ToString(); } + + [GeneratedRegex(@"^([a-zA-Z0-9\-_.]+)\s*(>=|<=|==|>|<|!=|~=)\s*(.+)$")] + private static partial Regex PackageSpecifierRegex(); } From 92a14638d833ac353932d11b95646a772735c403 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 15:47:54 -0400 Subject: [PATCH 231/297] Fix SafetensorMetadataDialog compiled binding errors --- .../Views/Dialogs/SafetensorMetadataDialog.axaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SafetensorMetadataDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SafetensorMetadataDialog.axaml index b30bd28b..c4e1a27a 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/SafetensorMetadataDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/SafetensorMetadataDialog.axaml @@ -66,7 +66,7 @@ Orientation="Horizontal" /> - + +public partial class TabContext : ObservableObject +{ + [ObservableProperty] + private HybridModelFile? _selectedModel; + + public event EventHandler? StateChanged; + + partial void OnSelectedModelChanged(HybridModelFile? value) + { + OnStateChanged(nameof(SelectedModel)); + } + + protected virtual void OnStateChanged(string propertyName) + { + StateChanged?.Invoke(this, new TabStateChangedEventArgs(propertyName)); + } + + public class TabStateChangedEventArgs(string propertyName) : EventArgs + { + public string PropertyName { get; } = propertyName; + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs index 087ee727..14b2cf2f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs @@ -387,7 +387,6 @@ partial void OnSelectedModelChanged(HybridModelFile? value) { // Update TabContext with the selected model tabContext.SelectedModel = value; - tabContext.NotifyStateChanged(nameof(TabContext.SelectedModel), value); if (!IsExtraNetworksEnabled) return; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs index 3fcd6719..33ed2db3 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs @@ -410,6 +410,13 @@ private void EditorCut(TextEditor? textEditor) textEditor?.Cut(); } + [RelayCommand] + private async Task AmplifyPrompt() + { + var dialog = DialogHelper.CreateMarkdownDialog(tabContext.SelectedModel?.RelativePath ?? "nothin"); + await dialog.ShowAsync(); + } + /// public override JsonObject SaveStateToJsonObject() { From b38fbd2d36a4aa43bef88689d0457333e9d82e02 Mon Sep 17 00:00:00 2001 From: jt Date: Sat, 12 Apr 2025 14:29:18 -0700 Subject: [PATCH 234/297] and app.axaml stuff --- StabilityMatrix.Avalonia/App.axaml | 1 - StabilityMatrix.Avalonia/App.axaml.cs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index 51cea93d..ca4a8171 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -89,7 +89,6 @@ - diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 54a9d11e..37c8750e 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -426,6 +426,9 @@ internal static IServiceCollection ConfigureServices() s => s.ServiceType.GetCustomAttributes().Any() ); + // Scope context for individual tabs + services.AddScoped(); + // Other services services.AddSingleton(); services.AddSingleton( From f074bc2507fb8306f2306ddef6d4e07efb3b0e54 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 17:32:09 -0400 Subject: [PATCH 235/297] Update SkiaExtensions.cs --- StabilityMatrix.Avalonia/Extensions/SkiaExtensions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/StabilityMatrix.Avalonia/Extensions/SkiaExtensions.cs b/StabilityMatrix.Avalonia/Extensions/SkiaExtensions.cs index 287cdce0..1b91c5d2 100644 --- a/StabilityMatrix.Avalonia/Extensions/SkiaExtensions.cs +++ b/StabilityMatrix.Avalonia/Extensions/SkiaExtensions.cs @@ -172,4 +172,15 @@ public static Bitmap ToAvaloniaBitmap(this SKImage image, Vector dpi) pixmap.RowBytes ); } + + public static PixelFormat ToAvaloniaPixelFormat(this SKColorType colorType) + { + // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault + return colorType switch + { + SKColorType.Rgba8888 => PixelFormat.Rgba8888, + SKColorType.Bgra8888 => PixelFormat.Bgra8888, + _ => throw new NotSupportedException($"Unsupported SKColorType: {colorType}") + }; + } } From 32c211e857f8eb5c0848e8de8a71f76035dc0fbc Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 18:05:49 -0400 Subject: [PATCH 236/297] Extract IServiceManager, refactor usages --- StabilityMatrix.Avalonia/App.axaml.cs | 2 +- .../Controls/BetterDownloadableComboBox.cs | 2 +- .../Controls/FADownloadableComboBox.cs | 2 +- .../DesignData/DesignData.cs | 6 +- .../DesignData/MockLaunchPageViewModel.cs | 25 ++-- .../Extensions/ServiceManagerExtensions.cs | 64 ++++++++++ .../Helpers/AttributeServiceInjector.cs | 6 +- .../Models/Inference/EditableModule.cs | 4 +- .../Services/IServiceManager.cs | 48 ++++++++ .../Services/ServiceManager.cs | 115 +++--------------- .../Base/InferenceGenerationViewModelBase.cs | 4 +- .../CheckpointBrowserCardViewModel.cs | 4 +- .../CivitAiBrowserViewModel.cs | 2 +- .../OpenModelDbBrowserViewModel.cs | 2 +- .../CheckpointFileViewModel.cs | 4 +- .../ViewModels/CheckpointsPageViewModel.cs | 2 +- .../ViewModels/Dialogs/LykosLoginViewModel.cs | 2 +- .../ViewModels/Dialogs/MaskEditorViewModel.cs | 2 +- .../OpenModelDbModelDetailsViewModel.cs | 2 +- .../Inference/ControlNetCardViewModel.cs | 4 +- .../Inference/FaceDetailerViewModel.cs | 4 +- .../Inference/ImageFolderCardViewModel.cs | 4 +- .../Inference/ImageGalleryCardViewModel.cs | 4 +- .../InferenceFluxTextToImageViewModel.cs | 2 +- .../InferenceImageToImageViewModel.cs | 2 +- .../InferenceImageToVideoViewModel.cs | 2 +- .../InferenceImageUpscaleViewModel.cs | 2 +- .../InferenceTextToImageViewModel.cs | 2 +- .../InferenceWanImageToVideoViewModel.cs | 2 +- .../InferenceWanTextToVideoViewModel.cs | 2 +- .../Inference/ModelCardViewModel.cs | 2 +- .../Inference/Modules/ControlNetModule.cs | 2 +- .../Modules/DiscreteModelSamplingModule.cs | 2 +- .../Inference/Modules/FaceDetailerModule.cs | 2 +- .../Inference/Modules/FluxGuidanceModule.cs | 2 +- .../Inference/Modules/FluxHiresFixModule.cs | 2 +- .../Inference/Modules/FreeUModule.cs | 2 +- .../Inference/Modules/HiresFixModule.cs | 2 +- .../Inference/Modules/LayerDiffuseModule.cs | 2 +- .../Inference/Modules/LoraModule.cs | 2 +- .../Inference/Modules/ModuleBase.cs | 4 +- .../Modules/PromptExpansionModule.cs | 2 +- .../Inference/Modules/RescaleCfgModule.cs | 2 +- .../Inference/Modules/SaveImageModule.cs | 2 +- .../Inference/Modules/UpscalerModule.cs | 2 +- .../Inference/PromptCardViewModel.cs | 2 +- .../Inference/SamplerCardViewModel.cs | 2 +- .../Inference/SelectImageCardViewModel.cs | 2 +- .../Inference/StackCardViewModel.cs | 2 +- .../Inference/StackEditableCardViewModel.cs | 4 +- .../Inference/StackExpanderViewModel.cs | 2 +- .../Inference/StackViewModelBase.cs | 4 +- .../Inference/UpscalerCardViewModel.cs | 4 +- .../Video/ImgToVidModelCardViewModel.cs | 2 +- .../Inference/WanModelCardViewModel.cs | 2 +- .../Inference/WanSamplerCardViewModel.cs | 2 +- .../ViewModels/InferenceViewModel.cs | 4 +- .../ViewModels/LaunchPageViewModel.cs | 4 +- .../ViewModels/MainWindowViewModel.cs | 4 +- .../ViewModels/OutputsPageViewModel.cs | 4 +- .../MainPackageManagerViewModel.cs | 4 +- .../PackageManager/PackageCardViewModel.cs | 2 +- .../PackageExtensionBrowserViewModel.cs | 4 +- .../ViewModels/PackageManagerViewModel.cs | 2 +- .../Settings/AccountSettingsViewModel.cs | 6 +- .../Settings/InferenceSettingsViewModel.cs | 2 +- .../Settings/MainSettingsViewModel.cs | 4 +- .../ViewModels/SettingsViewModel.cs | 2 +- 68 files changed, 230 insertions(+), 200 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs create mode 100644 StabilityMatrix.Avalonia/Services/IServiceManager.cs diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 37c8750e..6b9dc317 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -374,7 +374,7 @@ internal static void ConfigurePageViewModels(IServiceCollection services) new MainWindowViewModel( provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService>(), + provider.GetRequiredService>(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService>(), diff --git a/StabilityMatrix.Avalonia/Controls/BetterDownloadableComboBox.cs b/StabilityMatrix.Avalonia/Controls/BetterDownloadableComboBox.cs index 5ce8c8ff..d2f6ea5f 100644 --- a/StabilityMatrix.Avalonia/Controls/BetterDownloadableComboBox.cs +++ b/StabilityMatrix.Avalonia/Controls/BetterDownloadableComboBox.cs @@ -54,7 +54,7 @@ private static async Task PromptDownloadAsync(IDownloadableResource downloadable if (downloadable.DownloadableResource is not { } resource) return; - var vmFactory = App.Services.GetRequiredService>(); + var vmFactory = App.Services.GetRequiredService>(); var confirmDialog = vmFactory.Get(); confirmDialog.Resource = resource; confirmDialog.FileName = resource.FileName; diff --git a/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs b/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs index 3294afe7..c9f2cb6a 100644 --- a/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs +++ b/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs @@ -55,7 +55,7 @@ private static async Task PromptDownloadAsync(IDownloadableResource downloadable if (downloadable.DownloadableResource is not { } resource) return; - var vmFactory = App.Services.GetRequiredService>(); + var vmFactory = App.Services.GetRequiredService>(); var confirmDialog = vmFactory.Get(); confirmDialog.Resource = resource; confirmDialog.FileName = resource.FileName; diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index d3c81763..69657cb1 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -164,7 +164,7 @@ public static void Initialize() Services = services.BuildServiceProvider(); - var dialogFactory = Services.GetRequiredService>(); + var dialogFactory = Services.GetRequiredService>(); var settingsManager = Services.GetRequiredService(); var downloadService = Services.GetRequiredService(); var modelFinder = Services.GetRequiredService(); @@ -438,8 +438,8 @@ public static void Initialize() [NotNull] public static UpdateViewModel? UpdateViewModel { get; private set; } - public static ServiceManager DialogFactory => - Services.GetRequiredService>(); + public static IServiceManager DialogFactory => + Services.GetRequiredService>(); public static MainWindowViewModel MainWindowViewModel => Services.GetRequiredService(); diff --git a/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs b/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs index f91bdd97..7b203754 100644 --- a/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs +++ b/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs @@ -21,7 +21,7 @@ public MockLaunchPageViewModel( IPyRunner pyRunner, INotificationService notificationService, ISharedFolders sharedFolders, - ServiceManager dialogFactory + IServiceManager dialogFactory ) : base( logger, @@ -34,18 +34,18 @@ ServiceManager dialogFactory ) { } public override BasePackage? SelectedBasePackage => - SelectedPackage?.PackageName != "dank-diffusion" ? base.SelectedBasePackage : - new DankDiffusion(null!, null!, null!, null!); - + SelectedPackage?.PackageName != "dank-diffusion" + ? base.SelectedBasePackage + : new DankDiffusion(null!, null!, null!, null!); + protected override Task LaunchImpl(string? command) { IsLaunchTeachingTipsOpen = false; - RunningPackage = new PackagePair( - null!, - new DankDiffusion(null!, null!, null!, null!)); - - Console.Document.Insert(0, + RunningPackage = new PackagePair(null!, new DankDiffusion(null!, null!, null!, null!)); + + Console.Document.Insert( + 0, """ Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] Version: 1.5.0 @@ -53,15 +53,16 @@ Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit Fetching updates for midas... Checking out commit for midas with hash: 2e42b7f... - """); - + """ + ); + return Task.CompletedTask; } public override Task Stop() { RunningPackage = null; - + return Task.CompletedTask; } } diff --git a/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs b/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs new file mode 100644 index 00000000..a2a93535 --- /dev/null +++ b/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs @@ -0,0 +1,64 @@ +using System.ComponentModel; +using Avalonia.Controls; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Services; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Extensions; + +[Localizable(false)] +public static class ServiceManagerExtensions +{ + /// + /// Get a view model instance, set as DataContext of its View, and return + /// a BetterContentDialog with that View as its Content + /// + public static BetterContentDialog GetDialog(this IServiceManager serviceManager) + { + var instance = serviceManager.Get()!; + + if ( + Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) + is not ViewAttribute viewAttr + ) + { + throw new InvalidOperationException($"View not found for {instance.GetType().FullName}"); + } + + if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) + { + throw new NullReferenceException($"Unable to create instance for {instance.GetType().FullName}"); + } + + return new BetterContentDialog { Content = view }; + } + + /// + /// Get a view model instance with initializer, set as DataContext of its View, and return + /// a BetterContentDialog with that View as its Content + /// + public static BetterContentDialog GetDialog( + this IServiceManager serviceManager, + Action initializer + ) + { + var instance = serviceManager.Get(initializer)!; + + if ( + Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) + is not ViewAttribute viewAttr + ) + { + throw new InvalidOperationException($"View not found for {instance.GetType().FullName}"); + } + + if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) + { + throw new NullReferenceException($"Unable to create instance for {instance.GetType().FullName}"); + } + + view.DataContext = instance; + + return new BetterContentDialog { Content = view }; + } +} diff --git a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs index 311c1950..73274f29 100644 --- a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs +++ b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs @@ -50,9 +50,9 @@ public static void AddServicesByAttributesSourceGen(IServiceCollection services) } /// - /// Adds a to the . + /// Adds a to the . /// - /// The to which the will be added. + /// The to which the will be added. /// An optional filter for the services. /// The base type of the services. /// @@ -61,7 +61,7 @@ public static IServiceCollection AddServiceManagerWithCurrentCollectionServices< Func? serviceFilter = null ) { - return services.AddSingleton>(provider => + return services.AddSingleton>(provider => { using var _ = CodeTimer.StartDebug( callerName: $"{nameof(AddServiceManagerWithCurrentCollectionServices)}<{typeof(TService)}>" diff --git a/StabilityMatrix.Avalonia/Models/Inference/EditableModule.cs b/StabilityMatrix.Avalonia/Models/Inference/EditableModule.cs index e3150ce4..021d322a 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/EditableModule.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/EditableModule.cs @@ -47,9 +47,9 @@ public record EditableModule : StringValue }) ); - public Func, ViewModelBase> Build { get; } + public Func, ViewModelBase> Build { get; } - private EditableModule(string value, Func, ViewModelBase> build) + private EditableModule(string value, Func, ViewModelBase> build) : base(value) { Build = build; diff --git a/StabilityMatrix.Avalonia/Services/IServiceManager.cs b/StabilityMatrix.Avalonia/Services/IServiceManager.cs new file mode 100644 index 00000000..250179e8 --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/IServiceManager.cs @@ -0,0 +1,48 @@ +namespace StabilityMatrix.Avalonia.Services; + +public interface IServiceManager +{ + /// + /// Register a new dialog view model (singleton instance) + /// + IServiceManager Register(TService instance) + where TService : T; + + /// + /// Register a new dialog view model provider action (called on each dialog creation) + /// + IServiceManager Register(Func provider) + where TService : T; + + void Register(Type type, Func providerFunc); + + /// + /// Register a new dialog view model instance using a service provider + /// Equal to Register[TService](serviceProvider.GetRequiredService[TService]) + /// + IServiceManager RegisterProvider(IServiceProvider provider) + where TService : notnull, T; + + /// + /// Get a view model instance from runtime type + /// + T Get(Type serviceType); + + /// + /// Get a view model instance + /// + TService Get() + where TService : T; + + /// + /// Get a view model instance with an initializer parameter + /// + TService Get(Func initializer) + where TService : T; + + /// + /// Get a view model instance with an initializer for a mutable instance + /// + TService Get(Action initializer) + where TService : T; +} diff --git a/StabilityMatrix.Avalonia/Services/ServiceManager.cs b/StabilityMatrix.Avalonia/Services/ServiceManager.cs index 4b21c294..dfbb055a 100644 --- a/StabilityMatrix.Avalonia/Services/ServiceManager.cs +++ b/StabilityMatrix.Avalonia/Services/ServiceManager.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using Avalonia.Controls; using Microsoft.Extensions.DependencyInjection; -using StabilityMatrix.Avalonia.Controls; -using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Services; [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] -public class ServiceManager +[Localizable(false)] +public class ServiceManager : IServiceManager { // Holds providers private readonly Dictionary> providers = new(); @@ -20,7 +17,7 @@ public class ServiceManager /// /// Register a new dialog view model (singleton instance) /// - public ServiceManager Register(TService instance) + public IServiceManager Register(TService instance) where TService : T { if (instance is null) @@ -44,7 +41,7 @@ public ServiceManager Register(TService instance) /// /// Register a new dialog view model provider action (called on each dialog creation) /// - public ServiceManager Register(Func provider) + public IServiceManager Register(Func provider) where TService : T { lock (providers) @@ -68,7 +65,7 @@ public ServiceManager Register(Func provider) /// Register a new dialog view model instance using a service provider /// Equal to Register[TService](serviceProvider.GetRequiredService[TService]) /// - public ServiceManager RegisterProvider(IServiceProvider provider) + public IServiceManager RegisterProvider(IServiceProvider provider) where TService : notnull, T { lock (providers) @@ -96,18 +93,14 @@ public T Get(Type serviceType) { if (!serviceType.IsAssignableTo(typeof(T))) { - throw new ArgumentException( - $"Service type {serviceType} is not assignable to {typeof(T)}" - ); + throw new ArgumentException($"Service type {serviceType} is not assignable to {typeof(T)}"); } if (instances.TryGetValue(serviceType, out var instance)) { if (instance is null) { - throw new ArgumentException( - $"Service of type {serviceType} was registered as null" - ); + throw new ArgumentException($"Service of type {serviceType} was registered as null"); } return (T)instance; } @@ -116,23 +109,17 @@ public T Get(Type serviceType) { if (provider is null) { - throw new ArgumentException( - $"Service of type {serviceType} was registered as null" - ); + throw new ArgumentException($"Service of type {serviceType} was registered as null"); } var result = provider(); if (result is null) { - throw new ArgumentException( - $"Service provider for type {serviceType} returned null" - ); + throw new ArgumentException($"Service provider for type {serviceType} returned null"); } return (T)result; } - throw new ArgumentException( - $"Service of type {serviceType} is not registered for {typeof(T)}" - ); + throw new ArgumentException($"Service of type {serviceType} is not registered for {typeof(T)}"); } /// @@ -146,9 +133,7 @@ public TService Get() { if (instance is null) { - throw new ArgumentException( - $"Service of type {typeof(TService)} was registered as null" - ); + throw new ArgumentException($"Service of type {typeof(TService)} was registered as null"); } return (TService)instance; } @@ -157,23 +142,17 @@ public TService Get() { if (provider is null) { - throw new ArgumentException( - $"Service of type {typeof(TService)} was registered as null" - ); + throw new ArgumentException($"Service of type {typeof(TService)} was registered as null"); } var result = provider(); if (result is null) { - throw new ArgumentException( - $"Service provider for type {typeof(TService)} returned null" - ); + throw new ArgumentException($"Service provider for type {typeof(TService)} returned null"); } return (TService)result; } - throw new ArgumentException( - $"Service of type {typeof(TService)} is not registered for {typeof(T)}" - ); + throw new ArgumentException($"Service of type {typeof(TService)} is not registered for {typeof(T)}"); } /// @@ -197,75 +176,13 @@ public TService Get(Action initializer) return instance; } - /// - /// Get a view model instance, set as DataContext of its View, and return - /// a BetterContentDialog with that View as its Content - /// - public BetterContentDialog GetDialog() - where TService : T - { - var instance = Get()!; - - if ( - Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) - is not ViewAttribute viewAttr - ) - { - throw new InvalidOperationException( - $"View not found for {instance.GetType().FullName}" - ); - } - - if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) - { - throw new NullReferenceException( - $"Unable to create instance for {instance.GetType().FullName}" - ); - } - - return new BetterContentDialog { Content = view }; - } - - /// - /// Get a view model instance with initializer, set as DataContext of its View, and return - /// a BetterContentDialog with that View as its Content - /// - public BetterContentDialog GetDialog(Action initializer) - where TService : T - { - var instance = Get(initializer)!; - - if ( - Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) - is not ViewAttribute viewAttr - ) - { - throw new InvalidOperationException( - $"View not found for {instance.GetType().FullName}" - ); - } - - if (Activator.CreateInstance(viewAttr.ViewType) is not Control view) - { - throw new NullReferenceException( - $"Unable to create instance for {instance.GetType().FullName}" - ); - } - - view.DataContext = instance; - - return new BetterContentDialog { Content = view }; - } - public void Register(Type type, Func providerFunc) { lock (providers) { if (instances.ContainsKey(type) || providers.ContainsKey(type)) { - throw new ArgumentException( - $"Service of type {type} is already registered for {typeof(T)}" - ); + throw new ArgumentException($"Service of type {type} is already registered for {typeof(T)}"); } providers[type] = providerFunc; diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs index 0e3a985f..da454dfc 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs @@ -61,7 +61,7 @@ public abstract partial class InferenceGenerationViewModelBase private readonly ISettingsManager settingsManager; private readonly RunningPackageService runningPackageService; private readonly INotificationService notificationService; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; [JsonPropertyName("ImageGallery")] public ImageGalleryCardViewModel ImageGalleryCardViewModel { get; } @@ -77,7 +77,7 @@ public abstract partial class InferenceGenerationViewModelBase /// protected InferenceGenerationViewModelBase( - ServiceManager vmFactory, + IServiceManager vmFactory, IInferenceClientManager inferenceClientManager, INotificationService notificationService, ISettingsManager settingsManager, diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs index f23c8db0..68a134f1 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs @@ -38,7 +38,7 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel private readonly IDownloadService downloadService; private readonly ITrackedDownloadService trackedDownloadService; private readonly ISettingsManager settingsManager; - private readonly ServiceManager dialogFactory; + private readonly IServiceManager dialogFactory; private readonly INotificationService notificationService; private readonly IModelIndexService modelIndexService; private readonly IModelImportService modelImportService; @@ -87,7 +87,7 @@ public CheckpointBrowserCardViewModel( IDownloadService downloadService, ITrackedDownloadService trackedDownloadService, ISettingsManager settingsManager, - ServiceManager dialogFactory, + IServiceManager dialogFactory, INotificationService notificationService, IModelIndexService modelIndexService, IModelImportService modelImportService diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs index 2ad3b036..0ae9528a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs @@ -128,7 +128,7 @@ public sealed partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinit public CivitAiBrowserViewModel( ICivitApi civitApi, ISettingsManager settingsManager, - ServiceManager dialogFactory, + IServiceManager dialogFactory, ILiteDbContext liteDbContext, INotificationService notificationService, ICivitBaseModelTypeService baseModelTypeService diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs index 9bf0dc07..71b78c51 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs @@ -28,7 +28,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; [RegisterSingleton] public sealed partial class OpenModelDbBrowserViewModel( ILogger logger, - ServiceManager vmManager, + IServiceManager vmManager, OpenModelDbManager openModelDbManager, INotificationService notificationService ) : TabViewModelBase diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs index 574225b3..119799bb 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs @@ -60,7 +60,7 @@ public partial class CheckpointFileViewModel : SelectableViewModelBase private readonly IModelIndexService modelIndexService; private readonly INotificationService notificationService; private readonly IDownloadService downloadService; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; private readonly ILogger logger; public bool CanShowTriggerWords => CheckpointFile.ConnectedModelInfo?.TrainedWords?.Length > 0; @@ -73,7 +73,7 @@ public CheckpointFileViewModel( IModelIndexService modelIndexService, INotificationService notificationService, IDownloadService downloadService, - ServiceManager vmFactory, + IServiceManager vmFactory, ILogger logger, LocalModelFile checkpointFile ) diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs index 5b7dfaa9..5e52a82d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs @@ -62,7 +62,7 @@ public partial class CheckpointsPageViewModel( IMetadataImportService metadataImportService, IModelImportService modelImportService, OpenModelDbManager openModelDbManager, - ServiceManager dialogFactory, + IServiceManager dialogFactory, ICivitBaseModelTypeService baseModelTypeService ) : PageViewModelBase { diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs index dec7c39a..f933d133 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs @@ -22,7 +22,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; [RegisterTransient, ManagedService] public partial class LykosLoginViewModel( IAccountsService accountsService, - ServiceManager vmFactory + IServiceManager vmFactory ) : TaskDialogViewModelBase { [ObservableProperty] diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs index 2dc5a436..c409c78f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs @@ -29,7 +29,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; [RegisterTransient] [ManagedService] [View(typeof(MaskEditorDialog))] -public partial class MaskEditorViewModel(ServiceManager vmFactory) +public partial class MaskEditorViewModel(IServiceManager vmFactory) : LoadableViewModelBase, IDisposable { diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs index 1788eea0..67ec3293 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs @@ -33,7 +33,7 @@ public partial class OpenModelDbModelDetailsViewModel( IModelImportService modelImportService, INotificationService notificationService, ISettingsManager settingsManager, - ServiceManager dialogFactory + IServiceManager dialogFactory ) : ContentDialogViewModelBase { public class ModelResourceViewModel(IModelIndexService modelIndexService) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs index 53bb8036..66c754f9 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs @@ -26,7 +26,7 @@ public partial class ControlNetCardViewModel : LoadableViewModelBase { public const string ModuleKey = "ControlNet"; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; [ObservableProperty] [Required] @@ -67,7 +67,7 @@ public partial class ControlNetCardViewModel : LoadableViewModelBase public ControlNetCardViewModel( IInferenceClientManager clientManager, - ServiceManager vmFactory + IServiceManager vmFactory ) { this.vmFactory = vmFactory; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs index d701e74e..e5f4c20a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs @@ -18,7 +18,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [RegisterTransient] public partial class FaceDetailerViewModel : LoadableViewModelBase { - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; public const string ModuleKey = "FaceDetailer"; [ObservableProperty] @@ -133,7 +133,7 @@ public partial class FaceDetailerViewModel : LoadableViewModelBase /// public FaceDetailerViewModel( IInferenceClientManager clientManager, - ServiceManager vmFactory + IServiceManager vmFactory ) { this.vmFactory = vmFactory; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs index b387c7f8..49645937 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs @@ -44,7 +44,7 @@ public partial class ImageFolderCardViewModel : DisposableViewModelBase private readonly IImageIndexService imageIndexService; private readonly ISettingsManager settingsManager; private readonly INotificationService notificationService; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; [ObservableProperty] private string? searchQuery; @@ -63,7 +63,7 @@ public ImageFolderCardViewModel( IImageIndexService imageIndexService, ISettingsManager settingsManager, INotificationService notificationService, - ServiceManager vmFactory + IServiceManager vmFactory ) { this.logger = logger; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs index 82ef3d42..72b6a139 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs @@ -29,7 +29,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; public partial class ImageGalleryCardViewModel : ViewModelBase { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; [ObservableProperty] private bool isPreviewOverlayEnabled; @@ -56,7 +56,7 @@ public partial class ImageGalleryCardViewModel : ViewModelBase public bool CanNavigateForward => SelectedImageIndex < ImageSources.Count - 1; public ImageGalleryCardViewModel( - ServiceManager vmFactory, + IServiceManager vmFactory, ISettingsManager settingsManager ) { diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs index 282e7fc1..2969faa3 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs @@ -51,7 +51,7 @@ public class InferenceFluxTextToImageViewModel : InferenceGenerationViewModelBas public SeedCardViewModel SeedCardViewModel { get; } public InferenceFluxTextToImageViewModel( - ServiceManager vmFactory, + IServiceManager vmFactory, IInferenceClientManager inferenceClientManager, INotificationService notificationService, ISettingsManager settingsManager, diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs index 917415a9..82a47f39 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs @@ -25,7 +25,7 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel /// public InferenceImageToImageViewModel( - ServiceManager vmFactory, + IServiceManager vmFactory, IInferenceClientManager inferenceClientManager, INotificationService notificationService, ISettingsManager settingsManager, diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs index bae182e9..ddb43be7 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs @@ -62,7 +62,7 @@ public InferenceImageToVideoViewModel( INotificationService notificationService, IInferenceClientManager inferenceClientManager, ISettingsManager settingsManager, - ServiceManager vmFactory, + IServiceManager vmFactory, IModelIndexService modelIndexService, RunningPackageService runningPackageService ) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs index 853dfcf1..0fd2f0ab 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs @@ -60,7 +60,7 @@ public InferenceImageUpscaleViewModel( INotificationService notificationService, IInferenceClientManager inferenceClientManager, ISettingsManager settingsManager, - ServiceManager vmFactory, + IServiceManager vmFactory, RunningPackageService runningPackageService ) : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs index dd1d898b..e95022d7 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs @@ -63,7 +63,7 @@ public InferenceTextToImageViewModel( INotificationService notificationService, IInferenceClientManager inferenceClientManager, ISettingsManager settingsManager, - ServiceManager vmFactory, + IServiceManager vmFactory, IModelIndexService modelIndexService, RunningPackageService runningPackageService, TabContext tabContext diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanImageToVideoViewModel.cs index 765958ab..315551a2 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanImageToVideoViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanImageToVideoViewModel.cs @@ -16,7 +16,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; public class InferenceWanImageToVideoViewModel : InferenceWanTextToVideoViewModel { public InferenceWanImageToVideoViewModel( - ServiceManager vmFactory, + IServiceManager vmFactory, IInferenceClientManager inferenceClientManager, INotificationService notificationService, ISettingsManager settingsManager, diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanTextToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanTextToVideoViewModel.cs index 7a7f96b5..f1aea09a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanTextToVideoViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanTextToVideoViewModel.cs @@ -39,7 +39,7 @@ public class InferenceWanTextToVideoViewModel : InferenceGenerationViewModelBase public VideoOutputSettingsCardViewModel VideoOutputSettingsCardViewModel { get; } public InferenceWanTextToVideoViewModel( - ServiceManager vmFactory, + IServiceManager vmFactory, IInferenceClientManager inferenceClientManager, INotificationService notificationService, ISettingsManager settingsManager, diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs index 14b2cf2f..92405241 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs @@ -23,7 +23,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [RegisterTransient] public partial class ModelCardViewModel( IInferenceClientManager clientManager, - ServiceManager vmFactory, + IServiceManager vmFactory, TabContext tabContext ) : LoadableViewModelBase, IParametersLoadableState, IComfyStep { diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs index 9b7780c7..5882e511 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs @@ -19,7 +19,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; public class ControlNetModule : ModuleBase { /// - public ControlNetModule(ServiceManager vmFactory) + public ControlNetModule(IServiceManager vmFactory) : base(vmFactory) { Title = "ControlNet"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs index 4a80f5ed..9b0a1336 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs @@ -11,7 +11,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; [RegisterTransient] public class DiscreteModelSamplingModule : ModuleBase { - public DiscreteModelSamplingModule(ServiceManager vmFactory) + public DiscreteModelSamplingModule(IServiceManager vmFactory) : base(vmFactory) { Title = "Discrete Model Sampling"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs index 34f5232f..1b4200fd 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs @@ -26,7 +26,7 @@ public partial class FaceDetailerModule : ModuleBase, IValidatableModule /// public override IRelayCommand SettingsCommand => OpenSettingsDialogCommand; - public FaceDetailerModule(ServiceManager vmFactory) + public FaceDetailerModule(IServiceManager vmFactory) : base(vmFactory) { Title = "Face Detailer"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs index d74bacc5..c16c6ecd 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs @@ -10,7 +10,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; [RegisterTransient] public class FluxGuidanceModule : ModuleBase { - public FluxGuidanceModule(ServiceManager vmFactory) + public FluxGuidanceModule(IServiceManager vmFactory) : base(vmFactory) { Title = "Use Flux Guidance"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs index 3c3a0a63..73acefd1 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs @@ -11,7 +11,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; [ManagedService] [RegisterTransient] -public class FluxHiresFixModule(ServiceManager vmFactory) : HiresFixModule(vmFactory) +public class FluxHiresFixModule(IServiceManager vmFactory) : HiresFixModule(vmFactory) { protected override void OnApplyStep(ModuleApplyStepEventArgs e) { diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs index 1e86ecd6..2bc5dff9 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs @@ -13,7 +13,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; public class FreeUModule : ModuleBase { /// - public FreeUModule(ServiceManager vmFactory) + public FreeUModule(IServiceManager vmFactory) : base(vmFactory) { Title = "FreeU"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs index 5490466e..ec2c202a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs @@ -26,7 +26,7 @@ public partial class HiresFixModule : ModuleBase public override IRelayCommand SettingsCommand => OpenSettingsDialogCommand; /// - public HiresFixModule(ServiceManager vmFactory) + public HiresFixModule(IServiceManager vmFactory) : base(vmFactory) { Title = "HiresFix"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs index 48909567..257c953f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs @@ -11,7 +11,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; public class LayerDiffuseModule : ModuleBase { /// - public LayerDiffuseModule(ServiceManager vmFactory) + public LayerDiffuseModule(IServiceManager vmFactory) : base(vmFactory) { Title = "Layer Diffuse"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs index a78d19dd..7ddb2677 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs @@ -25,7 +25,7 @@ public partial class LoraModule : ModuleBase /// public override IRelayCommand SettingsCommand => OpenSettingsDialogCommand; - public LoraModule(ServiceManager vmFactory) + public LoraModule(IServiceManager vmFactory) : base(vmFactory) { Title = "Lora"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ModuleBase.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ModuleBase.cs index 3b38b0f6..9f303375 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ModuleBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ModuleBase.cs @@ -8,10 +8,10 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; public abstract class ModuleBase : StackExpanderViewModel, IComfyStep, IInputImageProvider { - protected readonly ServiceManager VmFactory; + protected readonly IServiceManager VmFactory; /// - protected ModuleBase(ServiceManager vmFactory) + protected ModuleBase(IServiceManager vmFactory) : base(vmFactory) { VmFactory = vmFactory; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs index 5c998fde..30f85516 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs @@ -12,7 +12,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; [RegisterTransient] public class PromptExpansionModule : ModuleBase { - public PromptExpansionModule(ServiceManager vmFactory) + public PromptExpansionModule(IServiceManager vmFactory) : base(vmFactory) { Title = "Prompt Expansion"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs index 3d6c6482..e1334d25 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs @@ -11,7 +11,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; [RegisterTransient] public class RescaleCfgModule : ModuleBase { - public RescaleCfgModule(ServiceManager vmFactory) + public RescaleCfgModule(IServiceManager vmFactory) : base(vmFactory) { Title = "CFG Rescale"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs index 934e475a..3e360252 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs @@ -13,7 +13,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; public class SaveImageModule : ModuleBase { /// - public SaveImageModule(ServiceManager vmFactory) + public SaveImageModule(IServiceManager vmFactory) : base(vmFactory) { Title = Resources.Label_SaveIntermediateImage; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs index 7639e33c..d7aff2d5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs @@ -14,7 +14,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; public class UpscalerModule : ModuleBase { /// - public UpscalerModule(ServiceManager vmFactory) + public UpscalerModule(IServiceManager vmFactory) : base(vmFactory) { Title = "Upscaler"; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs index 33ed2db3..0b068c0a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs @@ -67,7 +67,7 @@ public PromptCardViewModel( ITokenizerProvider tokenizerProvider, ISettingsManager settingsManager, IModelIndexService modelIndexService, - ServiceManager vmFactory, + IServiceManager vmFactory, SharedState sharedState, TabContext tabContext ) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs index ddcd62f0..2acf60f1 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs @@ -123,7 +123,7 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo public SamplerCardViewModel( IInferenceClientManager clientManager, - ServiceManager vmFactory + IServiceManager vmFactory ) { ClientManager = clientManager; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs index 49c38b85..2bc1ce20 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs @@ -32,7 +32,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [RegisterTransient] public partial class SelectImageCardViewModel( INotificationService notificationService, - ServiceManager vmFactory + IServiceManager vmFactory ) : LoadableViewModelBase, IDropTarget, IComfyStep, IInputImageProvider { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs index 04c2ca19..057a8c09 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs @@ -16,7 +16,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; public class StackCardViewModel : StackViewModelBase { /// - public StackCardViewModel(ServiceManager vmFactory) + public StackCardViewModel(IServiceManager vmFactory) : base(vmFactory) { } /// diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs index 63ead44c..e2e5e49c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs @@ -20,7 +20,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [RegisterTransient] public partial class StackEditableCardViewModel : StackViewModelBase, IComfyStep { - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; [ObservableProperty] [property: JsonIgnore] @@ -44,7 +44,7 @@ public partial class StackEditableCardViewModel : StackViewModelBase, IComfyStep public IReadOnlyList DefaultModules { get; set; } = Array.Empty(); /// - public StackEditableCardViewModel(ServiceManager vmFactory) + public StackEditableCardViewModel(IServiceManager vmFactory) : base(vmFactory) { this.vmFactory = vmFactory; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs index ffeaf705..ad8df873 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs @@ -44,7 +44,7 @@ public partial class StackExpanderViewModel : StackViewModelBase public virtual IRelayCommand? SettingsCommand { get; set; } /// - public StackExpanderViewModel(ServiceManager vmFactory) + public StackExpanderViewModel(IServiceManager vmFactory) : base(vmFactory) { } public override void OnContainerIndexChanged(int value) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs index 1ea56b7e..a1785158 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs @@ -12,11 +12,11 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; public abstract class StackViewModelBase : LoadableViewModelBase { - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; public AdvancedObservableList Cards { get; } = new(); - protected StackViewModelBase(ServiceManager vmFactory) + protected StackViewModelBase(IServiceManager vmFactory) { this.vmFactory = vmFactory; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs index 23bb63cf..87751b08 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs @@ -18,7 +18,7 @@ public partial class UpscalerCardViewModel : LoadableViewModelBase public const string ModuleKey = "Upscaler"; private readonly INotificationService notificationService; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; [ObservableProperty] private double scale = 2; @@ -31,7 +31,7 @@ public partial class UpscalerCardViewModel : LoadableViewModelBase public UpscalerCardViewModel( IInferenceClientManager clientManager, INotificationService notificationService, - ServiceManager vmFactory + IServiceManager vmFactory ) { this.notificationService = notificationService; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs index b8f3f81a..0b81ea63 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs @@ -16,7 +16,7 @@ public class ImgToVidModelCardViewModel : ModelCardViewModel { public ImgToVidModelCardViewModel( IInferenceClientManager clientManager, - ServiceManager vmFactory + IServiceManager vmFactory ) : base(clientManager, vmFactory) { diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs index ffdd269c..f3c68125 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs @@ -19,7 +19,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [RegisterTransient] public partial class WanModelCardViewModel( IInferenceClientManager clientManager, - ServiceManager vmFactory + IServiceManager vmFactory ) : LoadableViewModelBase, IParametersLoadableState, IComfyStep { [ObservableProperty] diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/WanSamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/WanSamplerCardViewModel.cs index bdcce8d3..369ea300 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/WanSamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/WanSamplerCardViewModel.cs @@ -18,7 +18,7 @@ public class WanSamplerCardViewModel : SamplerCardViewModel { public WanSamplerCardViewModel( IInferenceClientManager clientManager, - ServiceManager vmFactory + IServiceManager vmFactory ) : base(clientManager, vmFactory) { diff --git a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs index ed1b1f46..b7334a37 100644 --- a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs @@ -55,7 +55,7 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable private readonly INotificationService notificationService; private readonly ISettingsManager settingsManager; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; private readonly IModelIndexService modelIndexService; private readonly ILiteDbContext liteDbContext; private readonly IServiceScopeFactory scopeFactory; @@ -100,7 +100,7 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable private IDisposable? onStartupComplete; public InferenceViewModel( - ServiceManager vmFactory, + IServiceManager vmFactory, INotificationService notificationService, IInferenceClientManager inferenceClientManager, ISettingsManager settingsManager, diff --git a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs index 850e196f..45296d71 100644 --- a/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs @@ -48,7 +48,7 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn private readonly IPyRunner pyRunner; private readonly INotificationService notificationService; private readonly ISharedFolders sharedFolders; - private readonly ServiceManager dialogFactory; + private readonly IServiceManager dialogFactory; protected readonly IPackageFactory PackageFactory; // Regex to match if input contains a yes/no prompt, @@ -113,7 +113,7 @@ public LaunchPageViewModel( IPyRunner pyRunner, INotificationService notificationService, ISharedFolders sharedFolders, - ServiceManager dialogFactory + IServiceManager dialogFactory ) { this.logger = logger; diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index e0d0573b..ba19521c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs @@ -39,7 +39,7 @@ public partial class MainWindowViewModel : ViewModelBase private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly ISettingsManager settingsManager; - private readonly ServiceManager dialogFactory; + private readonly IServiceManager dialogFactory; private readonly ITrackedDownloadService trackedDownloadService; private readonly IDiscordRichPresenceService discordRichPresenceService; private readonly IModelIndexService modelIndexService; @@ -85,7 +85,7 @@ public partial class MainWindowViewModel : ViewModelBase public MainWindowViewModel( ISettingsManager settingsManager, IDiscordRichPresenceService discordRichPresenceService, - ServiceManager dialogFactory, + IServiceManager dialogFactory, ITrackedDownloadService trackedDownloadService, IModelIndexService modelIndexService, Lazy modelDownloadLinkHandler, diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs index e30b6c2d..c9da0fbd 100644 --- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs @@ -56,7 +56,7 @@ public partial class OutputsPageViewModel : PageViewModelBase private readonly INavigationService navigationService; private readonly ILogger logger; private readonly List cancellationTokenSources = []; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; public override string Title => Resources.Label_OutputsPageTitle; @@ -119,7 +119,7 @@ public OutputsPageViewModel( INotificationService notificationService, INavigationService navigationService, ILogger logger, - ServiceManager vmFactory + IServiceManager vmFactory ) { this.settingsManager = settingsManager; diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs index c701142b..654bab66 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs @@ -42,7 +42,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager; public partial class MainPackageManagerViewModel : PageViewModelBase { private readonly ISettingsManager settingsManager; - private readonly ServiceManager dialogFactory; + private readonly IServiceManager dialogFactory; private readonly INotificationService notificationService; private readonly INavigationService packageNavigationService; private readonly ILogger logger; @@ -72,7 +72,7 @@ public partial class MainPackageManagerViewModel : PageViewModelBase public MainPackageManagerViewModel( ISettingsManager settingsManager, - ServiceManager dialogFactory, + IServiceManager dialogFactory, INotificationService notificationService, INavigationService packageNavigationService, ILogger logger, diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index c4e4778d..f7a52419 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -45,7 +45,7 @@ public partial class PackageCardViewModel( INotificationService notificationService, ISettingsManager settingsManager, INavigationService navigationService, - ServiceManager vmFactory, + IServiceManager vmFactory, RunningPackageService runningPackageService ) : ProgressViewModel { diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs index 3c0b30db..b82fe574 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs @@ -48,7 +48,7 @@ public partial class PackageExtensionBrowserViewModel : ViewModelBase, IDisposab { private readonly INotificationService notificationService; private readonly ISettingsManager settingsManager; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; private readonly IPrerequisiteHelper prerequisiteHelper; private readonly CompositeDisposable cleanUp; @@ -116,7 +116,7 @@ public SearchCollection< public PackageExtensionBrowserViewModel( INotificationService notificationService, ISettingsManager settingsManager, - ServiceManager vmFactory, + IServiceManager vmFactory, IPrerequisiteHelper prerequisiteHelper ) { diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs index 50dbf799..372b0e79 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs @@ -32,7 +32,7 @@ public partial class PackageManagerViewModel : PageViewModelBase [ObservableProperty] private PageViewModelBase? currentPage; - public PackageManagerViewModel(ServiceManager vmFactory) + public PackageManagerViewModel(IServiceManager vmFactory) { SubPages = new PageViewModelBase[] { diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs index b3110928..8d39e8e2 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs @@ -10,8 +10,8 @@ using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Controls; using FluentIcons.Common; -using OpenIddict.Client; using Injectio.Attributes; +using OpenIddict.Client; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Services; @@ -39,7 +39,7 @@ public partial class AccountSettingsViewModel : PageViewModelBase { private readonly IAccountsService accountsService; private readonly ISettingsManager settingsManager; - private readonly ServiceManager vmFactory; + private readonly IServiceManager vmFactory; private readonly INotificationService notificationService; private readonly ILykosAuthApiV2 lykosAuthApi; @@ -74,7 +74,7 @@ public partial class AccountSettingsViewModel : PageViewModelBase public AccountSettingsViewModel( IAccountsService accountsService, ISettingsManager settingsManager, - ServiceManager vmFactory, + IServiceManager vmFactory, INotificationService notificationService, ILykosAuthApiV2 lykosAuthApi ) diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs index bf54fbdc..ef8ff425 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs @@ -83,7 +83,7 @@ public InferenceSettingsViewModel( INotificationService notificationService, IPrerequisiteHelper prerequisiteHelper, IPyRunner pyRunner, - ServiceManager dialogFactory, + IServiceManager dialogFactory, ICompletionProvider completionProvider, ITrackedDownloadService trackedDownloadService, IModelIndexService modelIndexService, diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs index 87c8e417..f394cc3a 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs @@ -74,7 +74,7 @@ public partial class MainSettingsViewModel : PageViewModelBase private readonly ISettingsManager settingsManager; private readonly IPrerequisiteHelper prerequisiteHelper; private readonly IPyRunner pyRunner; - private readonly ServiceManager dialogFactory; + private readonly IServiceManager dialogFactory; private readonly ICompletionProvider completionProvider; private readonly ITrackedDownloadService trackedDownloadService; private readonly IModelIndexService modelIndexService; @@ -205,7 +205,7 @@ public MainSettingsViewModel( ISettingsManager settingsManager, IPrerequisiteHelper prerequisiteHelper, IPyRunner pyRunner, - ServiceManager dialogFactory, + IServiceManager dialogFactory, ITrackedDownloadService trackedDownloadService, SharedState sharedState, ICompletionProvider completionProvider, diff --git a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs index 4b97904b..86e89a79 100644 --- a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs @@ -31,7 +31,7 @@ public partial class SettingsViewModel : PageViewModelBase [ObservableProperty] private PageViewModelBase? currentPage; - public SettingsViewModel(ServiceManager vmFactory) + public SettingsViewModel(IServiceManager vmFactory) { SubPages = new PageViewModelBase[] { From 7fecdbd58fac36bacbde84bd27da606580334c9a Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 18:06:45 -0400 Subject: [PATCH 237/297] Fix missing tabcontext cant build --- .../ViewModels/Inference/InferenceImageToImageViewModel.cs | 6 ++++-- .../Inference/Video/ImgToVidModelCardViewModel.cs | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs index 82a47f39..e211ecdd 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs @@ -30,7 +30,8 @@ public InferenceImageToImageViewModel( INotificationService notificationService, ISettingsManager settingsManager, IModelIndexService modelIndexService, - RunningPackageService runningPackageService + RunningPackageService runningPackageService, + TabContext tabContext ) : base( notificationService, @@ -38,7 +39,8 @@ RunningPackageService runningPackageService settingsManager, vmFactory, modelIndexService, - runningPackageService + runningPackageService, + tabContext ) { SelectImageCardViewModel = vmFactory.Get(vm => diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs index 0b81ea63..47bb2d51 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs @@ -16,9 +16,10 @@ public class ImgToVidModelCardViewModel : ModelCardViewModel { public ImgToVidModelCardViewModel( IInferenceClientManager clientManager, - IServiceManager vmFactory + IServiceManager vmFactory, + TabContext tabContext ) - : base(clientManager, vmFactory) + : base(clientManager, vmFactory, tabContext) { DisableSettings = true; } From e39e3fec9b03ab73fe5e8f0c9a3b2b1282e8ef7a Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 19:17:52 -0400 Subject: [PATCH 238/297] Add scoped service manager --- .../Extensions/ServiceManagerExtensions.cs | 25 +++ .../Helpers/AttributeServiceInjector.cs | 13 +- .../Services/IServiceManager.cs | 30 ++-- .../Services/IServiceManagerScope.cs | 10 ++ .../Services/ScopedServiceManager.cs | 168 ++++++++++++++++++ .../Services/ServiceManager.cs | 117 ++++++++---- .../Services/ServiceManagerScope.cs | 19 ++ 7 files changed, 333 insertions(+), 49 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Services/IServiceManagerScope.cs create mode 100644 StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs create mode 100644 StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs diff --git a/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs b/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs index a2a93535..7c9a1194 100644 --- a/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs +++ b/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs @@ -9,6 +9,31 @@ namespace StabilityMatrix.Avalonia.Extensions; [Localizable(false)] public static class ServiceManagerExtensions { + /// + /// Get a view model instance with an initializer parameter + /// + public static TService Get( + this IServiceManager serviceManager, + Func initializer + ) + { + var instance = serviceManager.Get(); + return initializer(instance); + } + + /// + /// Get a view model instance with an initializer for a mutable instance + /// + public static TService Get( + this IServiceManager serviceManager, + Action initializer + ) + { + var instance = serviceManager.Get(); + initializer(instance); + return instance; + } + /// /// Get a view model instance, set as DataContext of its View, and return /// a BetterContentDialog with that View as its Content diff --git a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs index 73274f29..26ab7a2c 100644 --- a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs +++ b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs @@ -61,13 +61,13 @@ public static IServiceCollection AddServiceManagerWithCurrentCollectionServices< Func? serviceFilter = null ) { - return services.AddSingleton>(provider => + return services.AddScoped>(provider => { using var _ = CodeTimer.StartDebug( callerName: $"{nameof(AddServiceManagerWithCurrentCollectionServices)}<{typeof(TService)}>" ); - var serviceManager = new ServiceManager(); + var serviceManager = new ServiceManager(provider); // Get registered services that are assignable to TService var serviceDescriptors = services.Where(s => s.ServiceType.IsAssignableTo(typeof(TService))); @@ -84,7 +84,14 @@ public static IServiceCollection AddServiceManagerWithCurrentCollectionServices< Debug.Assert(type is not null, "type is not null"); Debug.Assert(type.IsAssignableTo(typeof(TService)), "type is assignable to TService"); - serviceManager.Register(type, () => (TService)provider.GetRequiredService(type)); + if (service.Lifetime == ServiceLifetime.Scoped) + { + serviceManager.RegisterScoped(type, sp => (TService)sp.GetRequiredService(type)); + } + else + { + serviceManager.Register(type, () => (TService)provider.GetRequiredService(type)); + } } return serviceManager; diff --git a/StabilityMatrix.Avalonia/Services/IServiceManager.cs b/StabilityMatrix.Avalonia/Services/IServiceManager.cs index 250179e8..a648fcc9 100644 --- a/StabilityMatrix.Avalonia/Services/IServiceManager.cs +++ b/StabilityMatrix.Avalonia/Services/IServiceManager.cs @@ -1,5 +1,8 @@ -namespace StabilityMatrix.Avalonia.Services; +using System.ComponentModel; +namespace StabilityMatrix.Avalonia.Services; + +[Localizable(false)] public interface IServiceManager { /// @@ -23,6 +26,19 @@ IServiceManager Register(Func provider) IServiceManager RegisterProvider(IServiceProvider provider) where TService : notnull, T; + /// + /// Register a new service provider action with Scoped lifetime. + /// The factory is called once per scope. + /// + IServiceManager RegisterScoped(Func provider) + where TService : T; + + /// + /// Creates a new service scope. + /// + /// An IServiceManagerScope representing the created scope. + IServiceManagerScope CreateScope(); + /// /// Get a view model instance from runtime type /// @@ -35,14 +51,8 @@ TService Get() where TService : T; /// - /// Get a view model instance with an initializer parameter - /// - TService Get(Func initializer) - where TService : T; - - /// - /// Get a view model instance with an initializer for a mutable instance + /// Register a new service provider action with Scoped lifetime. + /// The factory is called once per scope. /// - TService Get(Action initializer) - where TService : T; + IServiceManager RegisterScoped(Type type, Func provider); } diff --git a/StabilityMatrix.Avalonia/Services/IServiceManagerScope.cs b/StabilityMatrix.Avalonia/Services/IServiceManagerScope.cs new file mode 100644 index 00000000..ab2b9e68 --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/IServiceManagerScope.cs @@ -0,0 +1,10 @@ +namespace StabilityMatrix.Avalonia.Services; + +public interface IServiceManagerScope : IDisposable +{ + /// + /// Provides access to an instance of IServiceManager for managing and retrieving services + /// of a specified type T within the associated scope. + /// + IServiceManager ServiceManager { get; } +} diff --git a/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs b/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs new file mode 100644 index 00000000..407d5ecd --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs @@ -0,0 +1,168 @@ +using System.Collections.Concurrent; + +namespace StabilityMatrix.Avalonia.Services; + +internal class ScopedServiceManager : IServiceManager, IDisposable +{ + private readonly ServiceManager parentManager; + private readonly IServiceProvider scopedServiceProvider; + private readonly ConcurrentDictionary _scopedInstances = new(); + private readonly List _disposables = new(); + private readonly List _asyncDisposables = new(); + private readonly Lock _lock = new(); // For creating scoped instances safely + + private bool _disposed; + + internal ScopedServiceManager(ServiceManager parentManager, IServiceProvider scopedServiceProvider) + { + this.parentManager = parentManager; + this.scopedServiceProvider = scopedServiceProvider; + + // Add ourselves as the materialized IServiceManager for this scope + // _scopedInstances[typeof(IServiceManager)] = this; + } + + // Delegate Register methods to the parent manager + + public IServiceManager Register(TService instance) + where TService : T + { + return parentManager.Register(instance); + } + + public IServiceManager Register(Func provider) + where TService : T + { + return parentManager.Register(provider); + } + + public void Register(Type type, Func providerFunc) + { + parentManager.Register(type, providerFunc); + } + + public IServiceManager RegisterProvider(IServiceProvider provider) + where TService : notnull, T + { + return parentManager.RegisterProvider(provider); + } + + public IServiceManager RegisterScoped(Func provider) + where TService : T + { + return parentManager.RegisterScoped(provider); + } + + public IServiceManager RegisterScoped(Type type, Func provider) + { + return parentManager.RegisterScoped(type, provider); + } + + public IServiceManagerScope CreateScope() + { + return parentManager.CreateScope(); + } + + public TService Get() + where TService : T + { + return (TService)Get(typeof(TService))!; + } + + public T Get(Type serviceType) + { + if (!typeof(T).IsAssignableFrom(serviceType)) // Ensure type compatibility + { + throw new ArgumentException($"Service type {serviceType} is not assignable to {typeof(T)}"); + } + + // 1. Check if instance already exists *in this scope* + if (_scopedInstances.TryGetValue(serviceType, out var scopedInstance)) + { + return scopedInstance; + } + + // 2. Check if it's a known *scoped* service type from the parent + if (parentManager.TryGetScopedProvider(serviceType, out var scopedProvider)) + { + // Lock to prevent multiple creations of the same scoped service concurrently within this scope + lock (_lock) + { + // Double-check if another thread created it while waiting for the lock + if (_scopedInstances.TryGetValue(serviceType, out scopedInstance)) + { + return scopedInstance; + } + + // Create the scoped instance using the factory from the parent + var newScopedInstance = scopedProvider(scopedServiceProvider); + if (newScopedInstance == null) + throw new InvalidOperationException($"Scoped provider for {serviceType} returned null."); + + if (!_scopedInstances.TryAdd(serviceType, newScopedInstance)) + { + // Should not happen due to outer check + lock, but defensive check + throw new InvalidOperationException( + $"Failed to add scoped instance for {serviceType}. Concurrency issue?" + ); + } + + // Track disposables created within this scope + if (newScopedInstance is IDisposable disposable) + _disposables.Add(disposable); + if (newScopedInstance is IAsyncDisposable asyncDisposable) + _asyncDisposables.Add(asyncDisposable); + + return newScopedInstance; + } + } + + // 3. If not scoped, delegate to the parent manager to resolve Singleton or Transient + // (Parent's Get will throw if the type isn't registered there either) + return parentManager.Get(serviceType); + } + + public void Dispose() + { + // Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + // Dispose synchronous disposables created *within this scope* + foreach (var disposable in _disposables) + { + try + { + disposable.Dispose(); + } + catch (Exception ex) + { /* Log error */ + } + } + _disposables.Clear(); + + // Handle async disposables created *within this scope* + foreach (var asyncDisposable in _asyncDisposables) + { + try + { + asyncDisposable.DisposeAsync().AsTask().Wait(); + } + catch (Exception ex) + { /* Log error */ + } + } + _asyncDisposables.Clear(); + + _scopedInstances.Clear(); // Clear instances held by this scope + } + _disposed = true; + } +} diff --git a/StabilityMatrix.Avalonia/Services/ServiceManager.cs b/StabilityMatrix.Avalonia/Services/ServiceManager.cs index dfbb055a..a48404d4 100644 --- a/StabilityMatrix.Avalonia/Services/ServiceManager.cs +++ b/StabilityMatrix.Avalonia/Services/ServiceManager.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System.Collections.Concurrent; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; @@ -6,7 +7,7 @@ namespace StabilityMatrix.Avalonia.Services; [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [Localizable(false)] -public class ServiceManager : IServiceManager +public class ServiceManager(IServiceProvider scopedServiceProvider) : IServiceManager { // Holds providers private readonly Dictionary> providers = new(); @@ -14,6 +15,9 @@ public class ServiceManager : IServiceManager // Holds singleton instances private readonly Dictionary instances = new(); + // Holds scoped providers (factories) + private readonly ConcurrentDictionary> scopedProviders = new(); + /// /// Register a new dialog view model (singleton instance) /// @@ -61,6 +65,62 @@ public IServiceManager Register(Func provider) return this; } + public void Register(Type type, Func providerFunc) + { + lock (providers) + { + if (instances.ContainsKey(type) || providers.ContainsKey(type)) + { + throw new ArgumentException($"Service of type {type} is already registered for {typeof(T)}"); + } + + providers[type] = providerFunc; + } + } + + /// + /// Register a new service provider action with Scoped lifetime. + /// The factory is called once per scope. + /// + public IServiceManager RegisterScoped(Func provider) + where TService : T + { + var type = typeof(TService); + + lock (providers) + { + if (instances.ContainsKey(type) || providers.ContainsKey(type)) + throw new ArgumentException( + $"Service of type {type} is already registered with a different lifetime." + ); + + if (!scopedProviders.TryAdd(type, sp => provider(sp))) // Store as base type T + throw new ArgumentException($"Service of type {type} is already registered as Scoped."); + } + + return this; + } + + /// + /// Register a new service provider action with Scoped lifetime. + /// The factory is called once per scope. + /// + public IServiceManager RegisterScoped(Type type, Func provider) + { + lock (providers) + { + if (instances.ContainsKey(type) || providers.ContainsKey(type)) + throw new ArgumentException( + $"Service of type {type} is already registered with a different lifetime." + ); + + if (!scopedProviders.TryAdd(type, provider)) // Store as base type T + throw new ArgumentException($"Service of type {type} is already registered as Scoped."); + } + + return this; + } + /// /// Register a new dialog view model instance using a service provider /// Equal to Register[TService](serviceProvider.GetRequiredService[TService]) @@ -85,6 +145,25 @@ public IServiceManager RegisterProvider(IServiceProvider provider) return this; } + /// + /// Creates a new service scope. + /// + /// An IServiceManagerScope representing the created scope. + public IServiceManagerScope CreateScope() + { + var scope = scopedServiceProvider.CreateScope(); + return new ServiceManagerScope(scope, new ScopedServiceManager(this, scope.ServiceProvider)); + } + + // Internal method for ScopedServiceManager to access providers + internal bool TryGetScopedProvider( + Type serviceType, + [MaybeNullWhen(false)] out Func provider + ) + { + return scopedProviders.TryGetValue(serviceType, out provider); + } + /// /// Get a view model instance from runtime type /// @@ -154,38 +233,4 @@ public TService Get() throw new ArgumentException($"Service of type {typeof(TService)} is not registered for {typeof(T)}"); } - - /// - /// Get a view model instance with an initializer parameter - /// - public TService Get(Func initializer) - where TService : T - { - var instance = Get(); - return initializer(instance); - } - - /// - /// Get a view model instance with an initializer for a mutable instance - /// - public TService Get(Action initializer) - where TService : T - { - var instance = Get(); - initializer(instance); - return instance; - } - - public void Register(Type type, Func providerFunc) - { - lock (providers) - { - if (instances.ContainsKey(type) || providers.ContainsKey(type)) - { - throw new ArgumentException($"Service of type {type} is already registered for {typeof(T)}"); - } - - providers[type] = providerFunc; - } - } } diff --git a/StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs b/StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs new file mode 100644 index 00000000..735ceaf1 --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace StabilityMatrix.Avalonia.Services; + +internal class ServiceManagerScope( + [HandlesResourceDisposal] IServiceScope scope, + [HandlesResourceDisposal] ScopedServiceManager serviceManager +) : IServiceManagerScope +{ + public IServiceManager ServiceManager { get; } = serviceManager; + + public void Dispose() + { + scope.Dispose(); + serviceManager.Dispose(); + GC.SuppressFinalize(this); + } +} From 1c381b2f08dee7e9eade66d9ac974d2a88f6a4e3 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 19:20:02 -0400 Subject: [PATCH 239/297] move to interface --- .../Extensions/ServiceManagerExtensions.cs | 25 ------------------- .../Services/IServiceManager.cs | 21 ++++++++++++++++ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs b/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs index 7c9a1194..a2a93535 100644 --- a/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs +++ b/StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs @@ -9,31 +9,6 @@ namespace StabilityMatrix.Avalonia.Extensions; [Localizable(false)] public static class ServiceManagerExtensions { - /// - /// Get a view model instance with an initializer parameter - /// - public static TService Get( - this IServiceManager serviceManager, - Func initializer - ) - { - var instance = serviceManager.Get(); - return initializer(instance); - } - - /// - /// Get a view model instance with an initializer for a mutable instance - /// - public static TService Get( - this IServiceManager serviceManager, - Action initializer - ) - { - var instance = serviceManager.Get(); - initializer(instance); - return instance; - } - /// /// Get a view model instance, set as DataContext of its View, and return /// a BetterContentDialog with that View as its Content diff --git a/StabilityMatrix.Avalonia/Services/IServiceManager.cs b/StabilityMatrix.Avalonia/Services/IServiceManager.cs index a648fcc9..32338c6f 100644 --- a/StabilityMatrix.Avalonia/Services/IServiceManager.cs +++ b/StabilityMatrix.Avalonia/Services/IServiceManager.cs @@ -50,6 +50,27 @@ IServiceManager RegisterScoped(Func pro TService Get() where TService : T; + /// + /// Get a view model instance with an initializer parameter + /// + public TService Get(Func initializer) + where TService : T + { + var instance = Get(); + return initializer(instance); + } + + /// + /// Get a view model instance with an initializer for a mutable instance + /// + public TService Get(Action initializer) + where TService : T + { + var instance = Get(); + initializer(instance); + return instance; + } + /// /// Register a new service provider action with Scoped lifetime. /// The factory is called once per scope. From da6fd1b778841a19afede7a5a1b94ea56b89e4c9 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 20:09:05 -0400 Subject: [PATCH 240/297] Fix scoped resolving, --- .../Helpers/AttributeServiceInjector.cs | 33 +++++- .../Services/ScopedServiceManager.cs | 101 ++---------------- .../Services/ServiceManagerScope.cs | 3 +- .../Services/TabContext.cs | 4 + .../ViewModels/InferenceViewModel.cs | 42 ++------ 5 files changed, 56 insertions(+), 127 deletions(-) diff --git a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs index 26ab7a2c..09233200 100644 --- a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs +++ b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs @@ -49,6 +49,17 @@ public static void AddServicesByAttributesSourceGen(IServiceCollection services) services.AddStabilityMatrixAvalonia(); } + private static bool IsScoped(IServiceProvider serviceProvider) + { + // Hacky check for if service provider is scoped + var typeName = serviceProvider.GetType().Name; + if (typeName == "ServiceProviderEngineScope" || typeName.Contains("Scope")) + { + return true; + } + return false; + } + /// /// Adds a to the . /// @@ -61,7 +72,8 @@ public static IServiceCollection AddServiceManagerWithCurrentCollectionServices< Func? serviceFilter = null ) { - return services.AddScoped>(provider => + // Register main service manager as singleton + services.AddSingleton>(provider => { using var _ = CodeTimer.StartDebug( callerName: $"{nameof(AddServiceManagerWithCurrentCollectionServices)}<{typeof(TService)}>" @@ -96,5 +108,24 @@ public static IServiceCollection AddServiceManagerWithCurrentCollectionServices< return serviceManager; }); + + // Register scoped for interface + services.AddScoped>(provider => + { + var rootServiceManager = provider.GetRequiredService>(); + + // For non scoped, just return the singleton + if (!IsScoped(provider)) + { + return rootServiceManager; + } + + // For scoped, create a new instance using root and provider + var scopedServiceManager = new ScopedServiceManager(rootServiceManager, provider); + + return scopedServiceManager; + }); + + return services; } } diff --git a/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs b/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs index 407d5ecd..dd29bba4 100644 --- a/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs +++ b/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs @@ -1,25 +1,14 @@ -using System.Collections.Concurrent; +namespace StabilityMatrix.Avalonia.Services; -namespace StabilityMatrix.Avalonia.Services; - -internal class ScopedServiceManager : IServiceManager, IDisposable +internal class ScopedServiceManager : IServiceManager { private readonly ServiceManager parentManager; private readonly IServiceProvider scopedServiceProvider; - private readonly ConcurrentDictionary _scopedInstances = new(); - private readonly List _disposables = new(); - private readonly List _asyncDisposables = new(); - private readonly Lock _lock = new(); // For creating scoped instances safely - - private bool _disposed; internal ScopedServiceManager(ServiceManager parentManager, IServiceProvider scopedServiceProvider) { this.parentManager = parentManager; this.scopedServiceProvider = scopedServiceProvider; - - // Add ourselves as the materialized IServiceManager for this scope - // _scopedInstances[typeof(IServiceManager)] = this; } // Delegate Register methods to the parent manager @@ -76,93 +65,19 @@ public T Get(Type serviceType) throw new ArgumentException($"Service type {serviceType} is not assignable to {typeof(T)}"); } - // 1. Check if instance already exists *in this scope* - if (_scopedInstances.TryGetValue(serviceType, out var scopedInstance)) - { - return scopedInstance; - } - - // 2. Check if it's a known *scoped* service type from the parent + // Check if it's a known *scoped* service type from the parent if (parentManager.TryGetScopedProvider(serviceType, out var scopedProvider)) { - // Lock to prevent multiple creations of the same scoped service concurrently within this scope - lock (_lock) - { - // Double-check if another thread created it while waiting for the lock - if (_scopedInstances.TryGetValue(serviceType, out scopedInstance)) - { - return scopedInstance; - } - - // Create the scoped instance using the factory from the parent - var newScopedInstance = scopedProvider(scopedServiceProvider); - if (newScopedInstance == null) - throw new InvalidOperationException($"Scoped provider for {serviceType} returned null."); - - if (!_scopedInstances.TryAdd(serviceType, newScopedInstance)) - { - // Should not happen due to outer check + lock, but defensive check - throw new InvalidOperationException( - $"Failed to add scoped instance for {serviceType}. Concurrency issue?" - ); - } + // Create the scoped instance using the factory from the parent + var newScopedInstance = scopedProvider(scopedServiceProvider); + if (newScopedInstance == null) + throw new InvalidOperationException($"Scoped provider for {serviceType} returned null."); - // Track disposables created within this scope - if (newScopedInstance is IDisposable disposable) - _disposables.Add(disposable); - if (newScopedInstance is IAsyncDisposable asyncDisposable) - _asyncDisposables.Add(asyncDisposable); - - return newScopedInstance; - } + return newScopedInstance; } // 3. If not scoped, delegate to the parent manager to resolve Singleton or Transient // (Parent's Get will throw if the type isn't registered there either) return parentManager.Get(serviceType); } - - public void Dispose() - { - // Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - - if (disposing) - { - // Dispose synchronous disposables created *within this scope* - foreach (var disposable in _disposables) - { - try - { - disposable.Dispose(); - } - catch (Exception ex) - { /* Log error */ - } - } - _disposables.Clear(); - - // Handle async disposables created *within this scope* - foreach (var asyncDisposable in _asyncDisposables) - { - try - { - asyncDisposable.DisposeAsync().AsTask().Wait(); - } - catch (Exception ex) - { /* Log error */ - } - } - _asyncDisposables.Clear(); - - _scopedInstances.Clear(); // Clear instances held by this scope - } - _disposed = true; - } } diff --git a/StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs b/StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs index 735ceaf1..0393f2fb 100644 --- a/StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs +++ b/StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs @@ -5,7 +5,7 @@ namespace StabilityMatrix.Avalonia.Services; internal class ServiceManagerScope( [HandlesResourceDisposal] IServiceScope scope, - [HandlesResourceDisposal] ScopedServiceManager serviceManager + ScopedServiceManager serviceManager ) : IServiceManagerScope { public IServiceManager ServiceManager { get; } = serviceManager; @@ -13,7 +13,6 @@ [HandlesResourceDisposal] ScopedServiceManager serviceManager public void Dispose() { scope.Dispose(); - serviceManager.Dispose(); GC.SuppressFinalize(this); } } diff --git a/StabilityMatrix.Avalonia/Services/TabContext.cs b/StabilityMatrix.Avalonia/Services/TabContext.cs index 5976e7b4..b28cee81 100644 --- a/StabilityMatrix.Avalonia/Services/TabContext.cs +++ b/StabilityMatrix.Avalonia/Services/TabContext.cs @@ -1,4 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; +using Injectio.Attributes; +using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Models; namespace StabilityMatrix.Avalonia.Services; @@ -6,6 +8,8 @@ namespace StabilityMatrix.Avalonia.Services; /// /// Holds shared state scoped to a single Inference tab. /// +[RegisterScoped] +[ManagedService] public partial class TabContext : ObservableObject { [ObservableProperty] diff --git a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs index b7334a37..8f274f9f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs @@ -58,10 +58,9 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable private readonly IServiceManager vmFactory; private readonly IModelIndexService modelIndexService; private readonly ILiteDbContext liteDbContext; - private readonly IServiceScopeFactory scopeFactory; private readonly RunningPackageService runningPackageService; private Guid? selectedPackageId; - private List scopes = []; + private List scopes = []; public override string Title => Resources.Label_Inference; public override IconSource IconSource => @@ -106,7 +105,6 @@ public InferenceViewModel( ISettingsManager settingsManager, IModelIndexService modelIndexService, ILiteDbContext liteDbContext, - IServiceScopeFactory scopeFactory, RunningPackageService runningPackageService, SharedState sharedState ) @@ -116,7 +114,6 @@ SharedState sharedState this.settingsManager = settingsManager; this.modelIndexService = modelIndexService; this.liteDbContext = liteDbContext; - this.scopeFactory = scopeFactory; this.runningPackageService = runningPackageService; ClientManager = inferenceClientManager; @@ -394,16 +391,11 @@ private void AddTab(InferenceProjectType type) } // Create a new scope for this tab - var scope = scopeFactory.CreateScope(); - scopes.Add(scope); - - // Register a TabContext in this scope - var tabContext = new TabContext(); - scope.ServiceProvider.GetRequiredService().AddScoped(_ => tabContext); + var scope = vmFactory.CreateScope(); // Get the view model using the scope's service provider var tab = - scope.ServiceProvider.GetService(vmType) as InferenceTabViewModelBase + scope.ServiceManager.Get(vmType) as InferenceTabViewModelBase ?? throw new NullReferenceException($"Could not create view model of type {vmType}"); Tabs.Add(tab); @@ -697,16 +689,12 @@ private async Task AddTabFromFile(FilePath file) } // Create a new scope for this tab - var scope = scopeFactory.CreateScope(); + var scope = vmFactory.CreateScope(); scopes.Add(scope); - // Register a TabContext in this scope - var tabContext = new TabContext(); - scope.ServiceProvider.GetRequiredService().AddScoped(_ => tabContext); - // Get the view model using the scope's service provider var vm = - scope.ServiceProvider.GetService(vmType) as InferenceTabViewModelBase + scope.ServiceManager.Get(vmType) as InferenceTabViewModelBase ?? throw new NullReferenceException($"Could not create view model of type {vmType}"); vm.LoadStateFromJsonObject(document.State); @@ -722,26 +710,18 @@ private async Task AddTabFromFile(FilePath file) private async Task AddTabFromFileAsync(LocalImageFile imageFile, InferenceProjectType projectType) { // Create a new scope for this tab - var scope = scopeFactory.CreateScope(); + var scope = vmFactory.CreateScope(); scopes.Add(scope); - // Register a TabContext in this scope - var tabContext = new TabContext(); - scope.ServiceProvider.GetRequiredService().AddScoped(_ => tabContext); - // Get the appropriate view model from the scope InferenceTabViewModelBase vm = projectType switch { - InferenceProjectType.TextToImage - => scope.ServiceProvider.GetRequiredService(), - InferenceProjectType.ImageToImage - => scope.ServiceProvider.GetRequiredService(), - InferenceProjectType.ImageToVideo - => scope.ServiceProvider.GetRequiredService(), - InferenceProjectType.Upscale - => scope.ServiceProvider.GetRequiredService(), + InferenceProjectType.TextToImage => scope.ServiceManager.Get(), + InferenceProjectType.ImageToImage => scope.ServiceManager.Get(), + InferenceProjectType.ImageToVideo => scope.ServiceManager.Get(), + InferenceProjectType.Upscale => scope.ServiceManager.Get(), InferenceProjectType.FluxTextToImage - => scope.ServiceProvider.GetRequiredService(), + => scope.ServiceManager.Get(), }; switch (vm) From 69653d3c485c4fddff3d8888c7fbb5864a186bff Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 12 Apr 2025 20:09:33 -0400 Subject: [PATCH 241/297] Change T2I and Model Prompt cards to scoped --- StabilityMatrix.Avalonia/App.axaml.cs | 3 --- .../ViewModels/Inference/InferenceTextToImageViewModel.cs | 2 +- .../ViewModels/Inference/ModelCardViewModel.cs | 2 +- .../ViewModels/Inference/PromptCardViewModel.cs | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 6b9dc317..b832147c 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -426,9 +426,6 @@ internal static IServiceCollection ConfigureServices() s => s.ServiceType.GetCustomAttributes().Any() ); - // Scope context for individual tabs - services.AddScoped(); - // Other services services.AddSingleton(); services.AddSingleton( diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs index e95022d7..072e7ff5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs @@ -29,7 +29,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(InferenceTextToImageView), IsPersistent = true)] [ManagedService] -[RegisterTransient] +[RegisterScoped] public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, IParametersLoadableState { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs index 92405241..df596bc9 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs @@ -20,7 +20,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(ModelCard))] [ManagedService] -[RegisterTransient] +[RegisterScoped] public partial class ModelCardViewModel( IInferenceClientManager clientManager, IServiceManager vmFactory, diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs index 0b068c0a..fb139a95 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs @@ -28,7 +28,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(PromptCard))] [ManagedService] -[RegisterTransient] +[RegisterScoped] public partial class PromptCardViewModel : DisposableLoadableViewModelBase, IParametersLoadableState, From 99a1a6a788e3af3dcd7174dd26c55ff2a495a88d Mon Sep 17 00:00:00 2001 From: jt Date: Sat, 12 Apr 2025 17:16:53 -0700 Subject: [PATCH 242/297] Add Forge-Classic & add disclaimer to reForge & add undo/redo to editor context menus --- CHANGELOG.md | 3 + .../Controls/EditorCommands.cs | 6 + .../Controls/EditorFlyouts.axaml | 49 +++-- .../Helper/Factory/PackageFactory.cs | 2 + .../Helper/HardwareInfo/GpuInfo.cs | 2 +- .../Models/Packages/ComfyUI.cs | 2 +- .../Models/Packages/ForgeClassic.cs | 179 ++++++++++++++++++ .../Models/Packages/Reforge.cs | 5 +- 8 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 StabilityMatrix.Core/Models/Packages/ForgeClassic.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ed52a5..0de6e023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ 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.14.0-pre.2 +### Added +- Added new package - [Stable Diffusion WebUI Forge - Classic](https://github.com/Haoming02/sd-webui-forge-classic) +- Added Undo/Redo commands to text editor context menus ### Changed - Updated install for kohya_ss to support RTX 5000-series GPUs ### Fixed diff --git a/StabilityMatrix.Avalonia/Controls/EditorCommands.cs b/StabilityMatrix.Avalonia/Controls/EditorCommands.cs index ba2e9097..1df19f3e 100644 --- a/StabilityMatrix.Avalonia/Controls/EditorCommands.cs +++ b/StabilityMatrix.Avalonia/Controls/EditorCommands.cs @@ -13,4 +13,10 @@ public static class EditorCommands public static RelayCommand PasteCommand { get; } = new(editor => editor?.Paste(), editor => editor?.CanPaste ?? false); + + public static RelayCommand UndoCommand { get; } = + new(editor => editor?.Undo(), editor => editor?.CanUndo ?? false); + + public static RelayCommand RedoCommand { get; } = + new(editor => editor?.Redo(), editor => editor?.CanRedo ?? false); } diff --git a/StabilityMatrix.Avalonia/Controls/EditorFlyouts.axaml b/StabilityMatrix.Avalonia/Controls/EditorFlyouts.axaml index e52851b1..d0dc2dd2 100644 --- a/StabilityMatrix.Avalonia/Controls/EditorFlyouts.axaml +++ b/StabilityMatrix.Avalonia/Controls/EditorFlyouts.axaml @@ -1,27 +1,40 @@ - - + + + CommandParameter="{Binding $parent[avaloniaEdit:TextEditor]}" + HotKey="Ctrl+V" + IconSource="Paste" + Text="Paste" /> + CommandParameter="{Binding $parent[avaloniaEdit:TextEditor]}" + HotKey="Ctrl+C" + IconSource="Copy" + Text="Copy" /> + CommandParameter="{Binding $parent[avaloniaEdit:TextEditor]}" + HotKey="Ctrl+X" + IconSource="Cut" + Text="Cut" /> + + diff --git a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs index b00f04b1..7630b55d 100644 --- a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs +++ b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs @@ -98,6 +98,8 @@ public BasePackage GetNewBasePackage(InstalledPackage installedPackage) => new ComfyZluda(githubApiCache, settingsManager, downloadService, prerequisiteHelper), "stable-diffusion-webui-amdgpu-forge" => new ForgeAmdGpu(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "forge-classic" + => new ForgeClassic(githubApiCache, settingsManager, downloadService, prerequisiteHelper), _ => throw new ArgumentOutOfRangeException(nameof(installedPackage)) }; } diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs index adfae7f8..ea4533c6 100644 --- a/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs +++ b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs @@ -37,7 +37,7 @@ public bool IsBlackwellGpu() && !Name.Contains("RTX 5000", StringComparison.OrdinalIgnoreCase); } - public bool IsTritonCompatibleGpu() + public bool IsAmpereOrNewerGpu() { if (Name is null) return false; diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 54fe7942..2cf03db6 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -308,7 +308,7 @@ IPrerequisiteHelper prerequisiteHelper [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps]; public override List GetExtraCommands() => - Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsTritonCompatibleGpu() is true + Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true ? [ new ExtraPackageCommand diff --git a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs new file mode 100644 index 00000000..f36e9ccd --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs @@ -0,0 +1,179 @@ +using Injectio.Attributes; +using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; +using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Progress; +using StabilityMatrix.Core.Processes; +using StabilityMatrix.Core.Python; +using StabilityMatrix.Core.Services; + +namespace StabilityMatrix.Core.Models.Packages; + +[RegisterSingleton(Duplicate = DuplicateStrategy.Append)] +public class ForgeClassic( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper +) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper) +{ + public override string Name => "forge-classic"; + public override string Author => "Haoming02"; + public override string RepositoryName => "sd-webui-forge-classic"; + public override string DisplayName { get; set; } = "Stable Diffusion WebUI Forge - Classic"; + public override string MainBranch => "classic"; + + public override string Blurb => + "This fork is focused exclusively on SD1 and SDXL checkpoints, having various optimizations implemented, with the main goal of being the lightest WebUI without any bloatwares."; + public override string LicenseUrl => + "https://github.com/Haoming02/sd-webui-forge-classic/blob/classic/LICENSE"; + public override Uri PreviewImageUri => + new("https://github.com/Haoming02/sd-webui-forge-classic/raw/classic/html/ui.webp"); + public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Recommended; + public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda]; + public override bool IsCompatible => HardwareHelper.HasNvidiaGpu(); + public override List LaunchOptions => + [ + new() + { + Name = "Host", + Type = LaunchOptionType.String, + DefaultValue = "localhost", + Options = ["--server-name"] + }, + new() + { + Name = "Port", + Type = LaunchOptionType.String, + DefaultValue = "7860", + Options = ["--port"] + }, + new() + { + Name = "Share", + Type = LaunchOptionType.Bool, + Description = "Set whether to share on Gradio", + Options = { "--share" } + }, + new() + { + Name = "Xformers", + Type = LaunchOptionType.Bool, + Description = "Set whether to use xformers", + Options = { "--xformers" } + }, + new() + { + Name = "Use SageAttention", + Type = LaunchOptionType.Bool, + Description = "Set whether to use sage attention", + Options = { "--sage" } + }, + new() + { + Name = "Pin Shared Memory", + Type = LaunchOptionType.Bool, + Options = { "--pin-shared-memory" }, + InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false + }, + new() + { + Name = "CUDA Malloc", + Type = LaunchOptionType.Bool, + Options = { "--cuda-malloc" }, + InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false + }, + new() + { + Name = "CUDA Stream", + Type = LaunchOptionType.Bool, + Options = { "--cuda-stream" }, + InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false + }, + new() + { + Name = "Auto Launch", + Type = LaunchOptionType.Bool, + Description = "Set whether to auto launch the webui", + Options = { "--auto-launch" } + }, + new() + { + Name = "Skip Python Version Check", + Type = LaunchOptionType.Bool, + Description = "Set whether to skip python version check", + Options = { "--skip-python-version-check" }, + InitialValue = true + }, + LaunchOptionDefinition.Extras, + ]; + + public override async Task InstallPackage( + string installLocation, + InstalledPackage installedPackage, + InstallPackageOptions options, + IProgress? progress = null, + Action? onConsoleOutput = null, + CancellationToken cancellationToken = default + ) + { + progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true)); + + await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false); + + await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); + + progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true)); + + var requirements = new FilePath(installLocation, "requirements.txt"); + var requirementsContent = await requirements + .ReadAllTextAsync(cancellationToken) + .ConfigureAwait(false); + + var pipArgs = new PipInstallArgs(); + + var isBlackwell = + SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu(); + var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion(); + + if (isBlackwell && torchVersion is TorchIndex.Cuda) + { + pipArgs = pipArgs + .AddArg("--pre") + .WithTorch() + .WithTorchVision() + .WithTorchAudio() + .WithTorchExtraIndex("nightly/cu128") + .AddArg("--upgrade"); + + if (installedPackage.PipOverrides != null) + { + pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); + } + progress?.Report( + new ProgressReport(-1f, "Installing Torch for your shiny new GPU...", isIndeterminate: true) + ); + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); + } + else + { + pipArgs = pipArgs.WithTorch().WithTorchVision().WithTorchExtraIndex("cu126"); + } + + if (isBlackwell && torchVersion is TorchIndex.Cuda) + { + pipArgs = new PipInstallArgs(); + } + + pipArgs = pipArgs.WithParsedFromRequirementsTxt(requirementsContent, excludePattern: "torch"); + + if (installedPackage.PipOverrides != null) + { + pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides); + } + + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); + progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false)); + } +} diff --git a/StabilityMatrix.Core/Models/Packages/Reforge.cs b/StabilityMatrix.Core/Models/Packages/Reforge.cs index baf02b77..d6d83fa4 100644 --- a/StabilityMatrix.Core/Models/Packages/Reforge.cs +++ b/StabilityMatrix.Core/Models/Packages/Reforge.cs @@ -22,6 +22,9 @@ IPrerequisiteHelper prerequisiteHelper public override string LicenseUrl => "https://github.com/Panchovix/stable-diffusion-webui-reForge/blob/main/LICENSE.txt"; public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/reforge/preview.webp"); + public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible; + public override bool OfferInOneClickInstaller => false; - public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended; + public override string Disclaimer => + "Development of this package has stopped. It may be removed from Stability Matrix in the future."; } From e46b2bce2d5bb25d48a81371ec6ce25d1fb8f856 Mon Sep 17 00:00:00 2001 From: jt Date: Sat, 12 Apr 2025 17:27:18 -0700 Subject: [PATCH 243/297] updated embeddings folder path for model sharing --- .../Models/Packages/ForgeClassic.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs index f36e9ccd..2226fb46 100644 --- a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs +++ b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs @@ -109,6 +109,28 @@ IPrerequisiteHelper prerequisiteHelper LaunchOptionDefinition.Extras, ]; + public override Dictionary> SharedFolders => + new() + { + [SharedFolderType.StableDiffusion] = ["models/Stable-diffusion/sd"], + [SharedFolderType.ESRGAN] = ["models/ESRGAN"], + [SharedFolderType.Lora] = ["models/Lora"], + [SharedFolderType.LyCORIS] = ["models/LyCORIS"], + [SharedFolderType.ApproxVAE] = ["models/VAE-approx"], + [SharedFolderType.VAE] = ["models/VAE"], + [SharedFolderType.DeepDanbooru] = ["models/deepbooru"], + [SharedFolderType.Embeddings] = ["models/embeddings"], + [SharedFolderType.Hypernetwork] = ["models/hypernetworks"], + [SharedFolderType.ControlNet] = ["models/controlnet/ControlNet"], + [SharedFolderType.AfterDetailer] = ["models/adetailer"], + [SharedFolderType.T2IAdapter] = ["models/controlnet/T2IAdapter"], + [SharedFolderType.IpAdapter] = ["models/controlnet/IpAdapter"], + [SharedFolderType.IpAdapters15] = ["models/controlnet/DiffusersIpAdapters"], + [SharedFolderType.IpAdaptersXl] = ["models/controlnet/DiffusersIpAdaptersXL"], + [SharedFolderType.TextEncoders] = ["models/text_encoder"], + [SharedFolderType.DiffusionModels] = ["models/Stable-diffusion/unet"], + }; + public override async Task InstallPackage( string installLocation, InstalledPackage installedPackage, From 06e58e634f71f54a5eb4bf45f37ae8c9e628ad40 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sun, 13 Apr 2025 22:17:14 -0400 Subject: [PATCH 244/297] Add button styles control theme --- StabilityMatrix.Avalonia/App.axaml | 1 + .../ButtonStyles.Accelerator.axaml | 215 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 StabilityMatrix.Avalonia/Styles/ControlThemes/ButtonStyles.Accelerator.axaml diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index 8b3f822e..b3c215bc 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -30,6 +30,7 @@ + diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/ButtonStyles.Accelerator.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/ButtonStyles.Accelerator.axaml new file mode 100644 index 00000000..d2a7507b --- /dev/null +++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/ButtonStyles.Accelerator.axaml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + #ff822d + #ff822d + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4ffa50459fe3c9bd97794a9c4d2adde75cd69b91 Mon Sep 17 00:00:00 2001 From: Ionite Date: Sun, 13 Apr 2025 23:10:06 -0400 Subject: [PATCH 245/297] Add AccountsService.RefreshLykosAsync --- StabilityMatrix.Avalonia/Services/AccountsService.cs | 7 +++++++ StabilityMatrix.Avalonia/Services/IAccountsService.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/StabilityMatrix.Avalonia/Services/AccountsService.cs b/StabilityMatrix.Avalonia/Services/AccountsService.cs index 897c505e..5df93502 100644 --- a/StabilityMatrix.Avalonia/Services/AccountsService.cs +++ b/StabilityMatrix.Avalonia/Services/AccountsService.cs @@ -181,6 +181,13 @@ public async Task RefreshAsync() await RefreshCivitAsync(secrets); } + public async Task RefreshLykosAsync() + { + var secrets = await secretsManager.SafeLoadAsync(); + + await RefreshLykosAsync(secrets); + } + private async Task RefreshLykosAsync(Secrets secrets) { if ( diff --git a/StabilityMatrix.Avalonia/Services/IAccountsService.cs b/StabilityMatrix.Avalonia/Services/IAccountsService.cs index 814b1578..742f1ac9 100644 --- a/StabilityMatrix.Avalonia/Services/IAccountsService.cs +++ b/StabilityMatrix.Avalonia/Services/IAccountsService.cs @@ -36,4 +36,6 @@ public interface IAccountsService Task CivitLogoutAsync(); Task RefreshAsync(); + + Task RefreshLykosAsync(); } From 872e42ec66eed4afdd8263d8fa587d28baff2173 Mon Sep 17 00:00:00 2001 From: jt Date: Sun, 13 Apr 2025 22:34:11 -0700 Subject: [PATCH 246/297] do the amplify --- .husky/task-runner.json | 6 + StabilityMatrix.Avalonia/App.axaml.cs | 24 ++ .../Controls/Inference/PromptCard.axaml | 276 +++++++++++++++++- .../Styles/ToggleButtonStyles.axaml | 135 +++++++-- .../Inference/BatchSizeCardViewModel.cs | 2 +- .../Inference/ControlNetCardViewModel.cs | 2 +- .../DiscreteModelSamplingCardViewModel.cs | 2 +- .../Inference/ExtraNetworkCardViewModel.cs | 2 +- .../Inference/FaceDetailerViewModel.cs | 2 +- .../Inference/FreeUCardViewModel.cs | 2 +- .../Inference/ImageFolderCardViewModel.cs | 2 +- .../Inference/ImageGalleryCardViewModel.cs | 2 +- .../InferenceFluxTextToImageViewModel.cs | 2 +- .../InferenceImageToImageViewModel.cs | 2 +- .../InferenceImageToVideoViewModel.cs | 2 +- .../InferenceImageUpscaleViewModel.cs | 2 +- .../InferenceWanImageToVideoViewModel.cs | 2 +- .../InferenceWanTextToVideoViewModel.cs | 2 +- .../Inference/LayerDiffuseCardViewModel.cs | 2 +- .../Inference/Modules/ControlNetModule.cs | 2 +- .../Modules/DiscreteModelSamplingModule.cs | 2 +- .../Inference/Modules/FaceDetailerModule.cs | 2 +- .../Inference/Modules/FluxGuidanceModule.cs | 2 +- .../Inference/Modules/FluxHiresFixModule.cs | 2 +- .../Inference/Modules/FreeUModule.cs | 2 +- .../Inference/Modules/HiresFixModule.cs | 2 +- .../Inference/Modules/LayerDiffuseModule.cs | 2 +- .../Inference/Modules/LoraModule.cs | 2 +- .../Modules/PromptExpansionModule.cs | 2 +- .../Inference/Modules/RescaleCfgModule.cs | 2 +- .../Inference/Modules/SaveImageModule.cs | 2 +- .../Inference/Modules/UpscalerModule.cs | 2 +- .../Inference/PromptCardViewModel.cs | 188 ++++++++++-- .../Inference/RescaleCfgCardViewModel.cs | 2 +- .../Inference/SamplerCardViewModel.cs | 2 +- .../ViewModels/Inference/SeedCardViewModel.cs | 2 +- .../Inference/SelectImageCardViewModel.cs | 2 +- .../Inference/SharpenCardViewModel.cs | 2 +- .../Inference/UnetModelCardViewModel.cs | 2 +- .../Inference/UpscalerCardViewModel.cs | 2 +- .../Video/ImgToVidModelCardViewModel.cs | 2 +- .../Video/SvdImgToVidConditioningViewModel.cs | 2 +- .../Video/VideoOutputSettingsCardViewModel.cs | 2 +- .../Inference/WanModelCardViewModel.cs | 2 +- .../Inference/WanSamplerCardViewModel.cs | 2 +- StabilityMatrix.Core/Api/PromptGen/.refitter | 16 + .../Api/PromptGen/Generated/Refitter.g.cs | 241 +++++++++++++++ StabilityMatrix.Core/Helper/Utilities.cs | 8 + .../StabilityMatrix.Core.csproj | 4 + 49 files changed, 889 insertions(+), 89 deletions(-) create mode 100644 StabilityMatrix.Core/Api/PromptGen/.refitter create mode 100644 StabilityMatrix.Core/Api/PromptGen/Generated/Refitter.g.cs diff --git a/.husky/task-runner.json b/.husky/task-runner.json index c8c0d739..e7b83c92 100644 --- a/.husky/task-runner.json +++ b/.husky/task-runner.json @@ -19,6 +19,12 @@ "group": "generate-openapi", "command": "dotnet", "args": ["refitter", "--settings-file", "./StabilityMatrix.Core/Api/LykosAuthApi/.refitter"] + }, + { + "name": "Run refitter for PromptGenApi", + "group": "generate-promptgen-openapi", + "command": "dotnet", + "args": ["refitter", "--settings-file", "./StabilityMatrix.Core/Api/PromptGen/.refitter"] } ] } diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index b832147c..ec0368e3 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -58,6 +58,7 @@ using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Core.Api; using StabilityMatrix.Core.Api.LykosAuthApi; +using StabilityMatrix.Core.Api.PromptGenApi; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Converters.Json; using StabilityMatrix.Core.Database; @@ -138,6 +139,14 @@ public sealed class App : Application #else public const string LykosAccountApiBaseUrl = "https://account.lykos.ai/"; #endif +#if DEBUG + // ReSharper disable twice LocalizableElement + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + public static string PromptGenApiBaseUrl => + Config?["PromptGenApiBaseUrl"] ?? "https://promptgen.lykos.ai/api"; +#else + public const string PromptGenApiBaseUrl = "https://promptgen.lykos.ai/api"; +#endif // ReSharper disable once MemberCanBePrivate.Global public IClassicDesktopStyleApplicationLifetime? DesktopLifetime => @@ -640,6 +649,21 @@ internal static IServiceCollection ConfigureServices() new TokenAuthHeaderHandler(serviceProvider.GetRequiredService()) ); + services + .AddRefitClient(defaultRefitSettings) + .ConfigureHttpClient(c => + { + c.BaseAddress = new Uri(PromptGenApiBaseUrl); + c.Timeout = TimeSpan.FromHours(1); + c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ""); + }) + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false }) + .AddPolicyHandler(retryPolicy) + .AddHttpMessageHandler( + serviceProvider => + new TokenAuthHeaderHandler(serviceProvider.GetRequiredService()) + ); + services .AddRefitClient(defaultRefitSettings) .ConfigureHttpClient(c => diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml index d29f68db..234e4463 100644 --- a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml @@ -1,9 +1,11 @@  + Classes="accent" + ToolTip.Tip="Prompt Amplifier"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +