diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index 6277e18c..db5fd66c 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -3,7 +3,6 @@ x:Class="StabilityMatrix.Avalonia.App" xmlns:local="using:StabilityMatrix.Avalonia" xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia" - RequestedThemeVariant="Default"> @@ -23,6 +22,7 @@ + @@ -32,5 +32,8 @@ + + + diff --git a/StabilityMatrix.Avalonia/Controls/SamplerCard.axaml b/StabilityMatrix.Avalonia/Controls/SamplerCard.axaml index ea790c44..6503cff0 100644 --- a/StabilityMatrix.Avalonia/Controls/SamplerCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/SamplerCard.axaml @@ -77,7 +77,7 @@ - + + ItemsSource="{Binding Cards}"> diff --git a/StabilityMatrix.Avalonia/Controls/StackExpander.axaml b/StabilityMatrix.Avalonia/Controls/StackExpander.axaml index edf04f5c..d7bdbf88 100644 --- a/StabilityMatrix.Avalonia/Controls/StackExpander.axaml +++ b/StabilityMatrix.Avalonia/Controls/StackExpander.axaml @@ -38,7 +38,7 @@ + ItemsSource="{Binding Cards}"> diff --git a/StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml b/StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml index 7cffaafc..7fdb1e2d 100644 --- a/StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml @@ -18,7 +18,7 @@ - + DialogFactory.Get(vm => { - vm.AddCards(new ViewModelBase[] + vm.AddCards(new LoadableViewModelBase[] { SamplerCardViewModel, SeedCardViewModel, @@ -413,7 +415,7 @@ public static class DesignData DialogFactory.Get(vm => { vm.Title = "Hires Fix"; - vm.AddCards(new ViewModelBase[] + vm.AddCards(new LoadableViewModelBase[] { SamplerCardViewModel, SeedCardViewModel, diff --git a/StabilityMatrix.Avalonia/Models/IJsonLoadableState.cs b/StabilityMatrix.Avalonia/Models/IJsonLoadableState.cs new file mode 100644 index 00000000..95d4860d --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/IJsonLoadableState.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Nodes; + +namespace StabilityMatrix.Avalonia.Models; + +public interface IJsonLoadableState +{ + void LoadStateFromJsonObject(JsonObject state); + + JsonObject SaveStateToJsonObject(); +} diff --git a/StabilityMatrix.Avalonia/Models/ILoadableState.cs b/StabilityMatrix.Avalonia/Models/ILoadableState.cs index c31401f8..cce10463 100644 --- a/StabilityMatrix.Avalonia/Models/ILoadableState.cs +++ b/StabilityMatrix.Avalonia/Models/ILoadableState.cs @@ -4,16 +4,23 @@ using System.Text.Json.Nodes; namespace StabilityMatrix.Avalonia.Models; -public interface ILoadableState +public interface ILoadableState : IJsonLoadableState { - public Type LoadableStateType => typeof(T); + new Type LoadableStateType => typeof(T); - public void LoadState(T state); + void LoadState(T state); - public void LoadStateFromJsonObject(JsonObject state) + new void LoadStateFromJsonObject(JsonObject state) { state.Deserialize(LoadableStateType); } - public T SaveState(); + T SaveState(); + + new JsonObject SaveStateToJsonObject() + { + var node = JsonSerializer.SerializeToNode(SaveState()); + return node?.AsObject() ?? throw new + InvalidOperationException("Failed to serialize state to JSON object."); + } } diff --git a/StabilityMatrix.Avalonia/Models/Inference/InferenceTextToImageModel.cs b/StabilityMatrix.Avalonia/Models/Inference/InferenceTextToImageModel.cs index df9ddaa2..5ad6717b 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/InferenceTextToImageModel.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/InferenceTextToImageModel.cs @@ -1,14 +1,13 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; namespace StabilityMatrix.Avalonia.Models.Inference; [JsonSerializable(typeof(InferenceTextToImageModel))] public class InferenceTextToImageModel { - public string? Prompt { get; init; } - public string? NegativePrompt { get; init; } public string? SelectedModelName { get; init; } - public SeedCardModel? SeedCardState { get; init; } - public SamplerCardModel? SamplerCardState { get; init; } - public PromptCardModel? PromptCardState { get; init; } + public JsonObject? SeedCardState { get; init; } + public JsonObject? PromptCardState { get; init; } + public JsonObject? StackCardState { get; init; } } diff --git a/StabilityMatrix.Avalonia/Models/Inference/StackCardModel.cs b/StabilityMatrix.Avalonia/Models/Inference/StackCardModel.cs new file mode 100644 index 00000000..295adb54 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/Inference/StackCardModel.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace StabilityMatrix.Avalonia.Models.Inference; + +[JsonSerializable(typeof(StackCardModel))] +public class StackCardModel +{ + public List? Cards { get; init; } +} diff --git a/StabilityMatrix.Avalonia/Models/Inference/StackExpanderModel.cs b/StabilityMatrix.Avalonia/Models/Inference/StackExpanderModel.cs new file mode 100644 index 00000000..5c782de4 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/Inference/StackExpanderModel.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using StabilityMatrix.Avalonia.ViewModels.Inference; + +namespace StabilityMatrix.Avalonia.Models.Inference; + +[JsonSerializable(typeof(StackExpanderModel))] +public class StackExpanderModel : StackCardModel +{ + public string? Title { get; set; } + public bool IsEnabled { get; set; } +} diff --git a/StabilityMatrix.Avalonia/Models/Inference/UpscalerCardModel.cs b/StabilityMatrix.Avalonia/Models/Inference/UpscalerCardModel.cs new file mode 100644 index 00000000..b454ca02 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/Inference/UpscalerCardModel.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using StabilityMatrix.Core.Models.Api.Comfy; + +namespace StabilityMatrix.Avalonia.Models.Inference; + +[JsonSerializable(typeof(UpscalerCardModel))] +public class UpscalerCardModel +{ + public double Scale { get; init; } = 1; + public ComfyUpscaler? SelectedUpscaler { get; init; } +} diff --git a/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs b/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs index 13eb0805..c06ac781 100644 --- a/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs +++ b/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs @@ -26,23 +26,19 @@ public class InferenceProjectDocument public JsonObject? State { get; set; } - public static InferenceProjectDocument FromLoadable(object loadableModel) + public static InferenceProjectDocument FromLoadable(IJsonLoadableState loadableModel) { - var document = new InferenceProjectDocument(); - - if (loadableModel is InferenceTextToImageViewModel model) - { - document.ProjectType = InferenceProjectType.TextToImage; - document.State = JsonSerializer.SerializeToNode(model.SaveState(), SerializerOptions)?.AsObject(); - } - else + return new InferenceProjectDocument { - throw new InvalidOperationException( - $"Unknown loadable model type: {loadableModel.GetType()}" - ); - } - - return document; + ProjectType = loadableModel switch + { + InferenceTextToImageViewModel => InferenceProjectType.TextToImage, + _ => throw new InvalidOperationException( + $"Unknown loadable model type: {loadableModel.GetType()}" + ) + }, + State = loadableModel.SaveStateToJsonObject() + }; } public Type GetViewModelType() diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs index 553a7ddc..a1c17c64 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using Avalonia.Media.Imaging; @@ -24,9 +25,7 @@ using StabilityMatrix.Core.Models.Api.Comfy.WebSocketData; namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(InferenceTextToImageView))] -public partial class InferenceTextToImageViewModel - : ViewModelBase, - ILoadableState +public partial class InferenceTextToImageViewModel : LoadableViewModelBase { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -36,11 +35,10 @@ public partial class InferenceTextToImageViewModel public IInferenceClientManager ClientManager { get; } public SeedCardViewModel SeedCardViewModel { get; } - public SamplerCardViewModel SamplerCardViewModel { get; } - public SamplerCardViewModel HiresFixSamplerCardViewModel { get; } + public ImageGalleryCardViewModel ImageGalleryCardViewModel { get; } public PromptCardViewModel PromptCardViewModel { get; } - public StackCardViewModel ConfigCardViewModel { get; } + public StackCardViewModel StackCardViewModel { get; } [ObservableProperty] private string? selectedModelName; @@ -70,36 +68,41 @@ public partial class InferenceTextToImageViewModel SeedCardViewModel = vmFactory.Get(); SeedCardViewModel.GenerateNewSeed(); - // SamplerCardViewModel = vmFactory.Get(); - HiresFixSamplerCardViewModel = vmFactory.Get(vm => - { - vm.IsScaleSizeMode = true; - vm.IsCfgScaleEnabled = false; - vm.IsSamplerSelectionEnabled = false; - vm.IsDenoiseStrengthEnabled = true; - }); + ImageGalleryCardViewModel = vmFactory.Get(); PromptCardViewModel = vmFactory.Get(); - ConfigCardViewModel = vmFactory.Get().WithCards(new ViewModelBase[] + StackCardViewModel = vmFactory.Get(); + + StackCardViewModel.AddCards(new LoadableViewModelBase[] + { + // Sampler + vmFactory.Get(), + // Hires Fix + vmFactory.Get(stackExpander => { - vmFactory.Get(), - vmFactory.Get().WithCards(new ViewModelBase[] + stackExpander.Title = "Hires Fix"; + stackExpander.AddCards(new LoadableViewModelBase[] { + // Hires Fix Upscaler vmFactory.Get(), - vmFactory.Get(vm => + // Hires Fix Sampler + vmFactory.Get(samplerCard => { - vm.IsScaleSizeMode = true; - vm.IsCfgScaleEnabled = false; - vm.IsSamplerSelectionEnabled = false; - vm.IsDenoiseStrengthEnabled = true; - }), - }) - }); + samplerCard.IsDimensionsEnabled = false; + samplerCard.IsCfgScaleEnabled = false; + samplerCard.IsSamplerSelectionEnabled = false; + samplerCard.IsDenoiseStrengthEnabled = true; + }) + }); + }) + }); } private Dictionary GetCurrentPrompt() { + var sampler = StackCardViewModel.GetCard(); + var prompt = new Dictionary { ["3"] = new() @@ -107,16 +110,16 @@ public partial class InferenceTextToImageViewModel ClassType = "KSampler", Inputs = new Dictionary { - ["cfg"] = SamplerCardViewModel.CfgScale, + ["cfg"] = sampler.CfgScale, ["denoise"] = 1, ["latent_image"] = new object[] { "5", 0 }, ["model"] = new object[] { "4", 0 }, ["negative"] = new object[] { "7", 0 }, ["positive"] = new object[] { "6", 0 }, - ["sampler_name"] = SamplerCardViewModel.SelectedSampler?.Name, + ["sampler_name"] = sampler.SelectedSampler?.Name, ["scheduler"] = "normal", ["seed"] = SeedCardViewModel.Seed, - ["steps"] = SamplerCardViewModel.Steps + ["steps"] = sampler.Steps } }, ["4"] = new() @@ -130,8 +133,8 @@ public partial class InferenceTextToImageViewModel Inputs = new Dictionary { ["batch_size"] = BatchSize, - ["height"] = SamplerCardViewModel.Height, - ["width"] = SamplerCardViewModel.Width, + ["height"] = sampler.Height, + ["width"] = sampler.Width, } }, ["6"] = new() @@ -299,33 +302,35 @@ public partial class InferenceTextToImageViewModel } /// - public void LoadState(InferenceTextToImageModel state) + public override void LoadStateFromJsonObject(JsonObject state) { - SelectedModelName = state.SelectedModelName; + var model = DeserializeModel(state); + + SelectedModelName = model.SelectedModelName; - if (state.SeedCardState != null) + if (model.StackCardState != null) { - SeedCardViewModel.LoadState(state.SeedCardState); + StackCardViewModel.LoadStateFromJsonObject(model.StackCardState); } - if (state.SamplerCardState != null) + if (model.SeedCardState != null) { - SamplerCardViewModel.LoadState(state.SamplerCardState); + SeedCardViewModel.LoadStateFromJsonObject(model.SeedCardState); } - if (state.PromptCardState != null) + if (model.PromptCardState != null) { - PromptCardViewModel.LoadState(state.PromptCardState); + PromptCardViewModel.LoadStateFromJsonObject(model.PromptCardState); } } /// - public InferenceTextToImageModel SaveState() + public override JsonObject SaveStateToJsonObject() { - return new InferenceTextToImageModel + return SerializeModel(new InferenceTextToImageModel { SelectedModelName = SelectedModelName, - SeedCardState = SeedCardViewModel.SaveState(), - SamplerCardState = SamplerCardViewModel.SaveState(), - PromptCardState = PromptCardViewModel.SaveState(), - }; + StackCardState = StackCardViewModel.SaveStateToJsonObject(), + SeedCardState = SeedCardViewModel.SaveStateToJsonObject(), + PromptCardState = PromptCardViewModel.SaveStateToJsonObject() + }); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs index 9f41040e..f1d70350 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs @@ -1,31 +1,33 @@ -using AvaloniaEdit.Document; +using System.Text.Json.Nodes; +using AvaloniaEdit.Document; using StabilityMatrix.Avalonia.Controls; -using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Models.Inference; using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(PromptCard))] -public class PromptCardViewModel : ViewModelBase, ILoadableState +public class PromptCardViewModel : LoadableViewModelBase { public TextDocument PromptDocument { get; } = new(); public TextDocument NegativePromptDocument { get; } = new(); /// - public void LoadState(PromptCardModel state) + public override void LoadStateFromJsonObject(JsonObject state) { - PromptDocument.Text = state.Prompt ?? ""; - NegativePromptDocument.Text = state.NegativePrompt ?? ""; + var model = DeserializeModel(state); + + PromptDocument.Text = model.Prompt ?? ""; + NegativePromptDocument.Text = model.NegativePrompt ?? ""; } /// - public PromptCardModel SaveState() + public override JsonObject SaveStateToJsonObject() { - return new PromptCardModel + return SerializeModel(new PromptCardModel { Prompt = PromptDocument.Text, NegativePrompt = NegativePromptDocument.Text - }; + }); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs index f1150032..c285c2ec 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Nodes; using CommunityToolkit.Mvvm.ComponentModel; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Models; @@ -10,7 +11,7 @@ using StabilityMatrix.Core.Models.Api.Comfy; namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(SamplerCard))] -public partial class SamplerCardViewModel : ViewModelBase, ILoadableState +public partial class SamplerCardViewModel : LoadableViewModelBase { [ObservableProperty] private int steps = 20; @@ -37,25 +38,27 @@ public partial class SamplerCardViewModel : ViewModelBase, ILoadableState - public void LoadState(SamplerCardModel state) + public override void LoadStateFromJsonObject(JsonObject state) { - Steps = state.Steps; - IsDenoiseStrengthEnabled = state.IsDenoiseStrengthEnabled; - DenoiseStrength = state.DenoiseStrength; - IsCfgScaleEnabled = state.IsCfgScaleEnabled; - CfgScale = state.CfgScale; - IsDimensionsEnabled = state.IsDimensionsEnabled; - Width = state.Width; - Height = state.Height; - IsSamplerSelectionEnabled = state.IsSamplerSelectionEnabled; - SelectedSampler = state.SelectedSampler is null ? null - : new ComfySampler(state.SelectedSampler); + var model = DeserializeModel(state); + + Steps = model.Steps; + IsDenoiseStrengthEnabled = model.IsDenoiseStrengthEnabled; + DenoiseStrength = model.DenoiseStrength; + IsCfgScaleEnabled = model.IsCfgScaleEnabled; + CfgScale = model.CfgScale; + IsDimensionsEnabled = model.IsDimensionsEnabled; + Width = model.Width; + Height = model.Height; + IsSamplerSelectionEnabled = model.IsSamplerSelectionEnabled; + SelectedSampler = model.SelectedSampler is null ? null + : new ComfySampler(model.SelectedSampler); } /// - public SamplerCardModel SaveState() + public override JsonObject SaveStateToJsonObject() { - return new SamplerCardModel + return SerializeModel(new SamplerCardModel { Steps = Steps, IsDenoiseStrengthEnabled = IsDenoiseStrengthEnabled, @@ -67,6 +70,6 @@ public partial class SamplerCardViewModel : ViewModelBase, ILoadableState +public partial class SeedCardViewModel : LoadableViewModelBase { [ObservableProperty, NotifyPropertyChangedFor(nameof(RandomizeButtonToolTip))] private bool isRandomizeEnabled = true; @@ -28,19 +28,21 @@ public partial class SeedCardViewModel : ViewModelBase, ILoadableState - public void LoadState(SeedCardModel state) + public override void LoadStateFromJsonObject(JsonObject state) { - Seed = long.TryParse(state.Seed, out var result) ? result : 0; - IsRandomizeEnabled = state.IsRandomizeEnabled; + var model = DeserializeModel(state); + + Seed = long.TryParse(model.Seed, out var result) ? result : 0; + IsRandomizeEnabled = model.IsRandomizeEnabled; } /// - public SeedCardModel SaveState() + public override JsonObject SaveStateToJsonObject() { - return new SeedCardModel + return SerializeModel(new SeedCardModel { Seed = Seed.ToString(), IsRandomizeEnabled = IsRandomizeEnabled - }; + }); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs index cdf20913..763054b2 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs @@ -1,46 +1,37 @@ -using System; -using System.Collections.Generic; -using Avalonia.Collections; +using System.Linq; +using System.Text.Json.Nodes; using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Models.Inference; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(StackCard))] -public class StackCardViewModel : ViewModelBase +public class StackCardViewModel : StackViewModelBase { - private readonly Dictionary> viewModelManager = new(); - - public AvaloniaList ConfigCards { get; } = new(); - - /// - /// Register new cards - /// - public void AddCards(IEnumerable cards) + /// + public override void LoadStateFromJsonObject(JsonObject state) { - foreach (var card in cards) + var model = DeserializeModel(state); + + if (model.Cards is null) return; + + foreach (var (i, card) in model.Cards.Enumerate()) { - var list = viewModelManager.GetOrAdd(card.GetType()); - list.Add(card); - ConfigCards.Add(card); + // Ignore if more than cards than we have + if (i > Cards.Count - 1) break; + + Cards[i].LoadStateFromJsonObject(card); } } - - /// - /// Registers new cards and returns self - /// - public StackCardViewModel WithCards(IEnumerable cards) - { - AddCards(cards); - return this; - } - - /// - /// Gets a card by type at specified index - /// - public T GetCard(int index = 0) where T : ViewModelBase + + /// + public override JsonObject SaveStateToJsonObject() { - return (T) viewModelManager[typeof(T)][index]; + return SerializeModel(new StackCardModel + { + Cards = Cards.Select(x => x.SaveStateToJsonObject()).ToList() + }); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs index 6acd1298..f6366ece 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs @@ -1,12 +1,45 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System.Linq; +using System.Text.Json.Nodes; +using CommunityToolkit.Mvvm.ComponentModel; using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Models.Inference; using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Extensions; namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(StackExpander))] -public partial class StackExpanderViewModel : StackCardViewModel +public partial class StackExpanderViewModel : StackViewModelBase { [ObservableProperty] private string? title; [ObservableProperty] private bool isEnabled; + + /// + public override void LoadStateFromJsonObject(JsonObject state) + { + var model = DeserializeModel(state); + Title = model.Title; + IsEnabled = model.IsEnabled; + + if (model.Cards is null) return; + + foreach (var (i, card) in model.Cards.Enumerate()) + { + // Ignore if more than cards than we have + if (i > Cards.Count - 1) break; + + Cards[i].LoadStateFromJsonObject(card); + } + } + + /// + public override JsonObject SaveStateToJsonObject() + { + return SerializeModel(new StackExpanderModel + { + Title = Title, + IsEnabled = IsEnabled, + Cards = Cards.Select(x => x.SaveStateToJsonObject()).ToList() + }); + } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs new file mode 100644 index 00000000..c2e41e7f --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Avalonia.Collections; +using StabilityMatrix.Core.Extensions; + +namespace StabilityMatrix.Avalonia.ViewModels.Inference; + +public abstract class StackViewModelBase : LoadableViewModelBase +{ + private readonly Dictionary> viewModelManager = new(); + + public AvaloniaList Cards { get; } = new(); + + /// + /// Register new cards + /// + public void AddCards(IEnumerable cards) + { + foreach (var card in cards) + { + var list = viewModelManager.GetOrAdd(card.GetType()); + list.Add(card); + Cards.Add(card); + } + } + + /// + /// Registers new cards and returns self + /// + public StackViewModelBase WithCards(IEnumerable cards) + { + AddCards(cards); + return this; + } + + /// + /// Gets a card by type at specified index + /// + public T GetCard(int index = 0) where T : LoadableViewModelBase + { + return (T) viewModelManager[typeof(T)][index]; + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs index 0a5d0452..1933efa6 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs @@ -1,11 +1,43 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System.Text.Json.Nodes; +using CommunityToolkit.Mvvm.ComponentModel; using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Models.Inference; +using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models.Api.Comfy; namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(UpscalerCard))] -public partial class UpscalerCardViewModel : ViewModelBase +public partial class UpscalerCardViewModel : LoadableViewModelBase { [ObservableProperty] private double scale = 1; + + [ObservableProperty] private ComfyUpscaler? selectedUpscaler; + + public IInferenceClientManager ClientManager { get; } + + public UpscalerCardViewModel(IInferenceClientManager clientManager) + { + ClientManager = clientManager; + } + + /// + public override void LoadStateFromJsonObject(JsonObject state) + { + var model = DeserializeModel(state); + + Scale = model.Scale; + SelectedUpscaler = model.SelectedUpscaler; + } + + /// + public override JsonObject SaveStateToJsonObject() + { + return SerializeModel(new UpscalerCardModel + { + Scale = Scale, + SelectedUpscaler = SelectedUpscaler + }); + } } diff --git a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs index c2b6ac21..a27659e7 100644 --- a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Notifications; using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Dock.Model.Mvvm.Controls; using FluentAvalonia.UI.Controls; using NLog; using StabilityMatrix.Avalonia.Models; @@ -50,10 +51,10 @@ public partial class InferenceViewModel : PageViewModelBase public IInferenceClientManager ClientManager { get; } - public AvaloniaList Tabs { get; } = new(); + public AvaloniaList Tabs { get; } = new(); [ObservableProperty] - private ViewModelBase? selectedTab; + private LoadableViewModelBase? selectedTab; [ObservableProperty] private PackagePair? runningPackage; @@ -110,7 +111,7 @@ public partial class InferenceViewModel : PageViewModelBase /// public void OnTabCloseRequested(TabViewTabCloseRequestedEventArgs e) { - if (e.Item is ViewModelBase vm) + if (e.Item is LoadableViewModelBase vm) { Tabs.Remove(vm); } @@ -259,17 +260,17 @@ public partial class InferenceViewModel : PageViewModelBase await using var stream = await file.OpenReadAsync(); var document = await JsonSerializer.DeserializeAsync(stream); - if (document == null) + if (document is null) { Logger.Warn("MenuOpenProject: Deserialize project file returned null"); return; } - ViewModelBase? vm = null; - if (document.ProjectType is InferenceProjectType.TextToImage) + LoadableViewModelBase? vm = null; + if (document.ProjectType is InferenceProjectType.TextToImage && document.State is not null) { var textToImage = vmFactory.Get(); - textToImage.LoadState(document.State.Deserialize()!); + textToImage.LoadStateFromJsonObject(document.State); vm = textToImage; } diff --git a/StabilityMatrix.Avalonia/ViewModels/LoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/LoadableViewModelBase.cs new file mode 100644 index 00000000..2c501715 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/LoadableViewModelBase.cs @@ -0,0 +1,34 @@ +using System; +using System.Text.Json; +using System.Text.Json.Nodes; +using StabilityMatrix.Avalonia.Models; + +namespace StabilityMatrix.Avalonia.ViewModels; + +public abstract class LoadableViewModelBase : ViewModelBase, IJsonLoadableState +{ + /// + public abstract void LoadStateFromJsonObject(JsonObject state); + + /// + public abstract JsonObject SaveStateToJsonObject(); + + /// + /// Serialize a model to a JSON object. + /// + protected static JsonObject SerializeModel(T model) + { + var node = JsonSerializer.SerializeToNode(model); + return node?.AsObject() ?? throw new + NullReferenceException("Failed to serialize state to JSON object."); + } + + /// + /// Deserialize a model from a JSON object. + /// + protected static T DeserializeModel(JsonObject state) + { + return state.Deserialize() ?? throw new + NullReferenceException("Failed to deserialize state from JSON object."); + } +} diff --git a/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs b/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs index e357ff6d..f94b4018 100644 --- a/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs @@ -1,4 +1,5 @@ -using Avalonia.Markup.Xaml; +using Avalonia.Input; +using Avalonia.Markup.Xaml; using FluentAvalonia.UI.Controls; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.ViewModels; @@ -10,6 +11,8 @@ public partial class InferencePage : UserControlBase public InferencePage() { InitializeComponent(); + AddHandler(DragDrop.DropEvent, DropHandler); + AddHandler(DragDrop.DragOverEvent, DragOverHandler); } private void InitializeComponent() @@ -21,4 +24,20 @@ public partial class InferencePage : UserControlBase { (DataContext as InferenceViewModel)?.OnTabCloseRequested(args); } + + private void DragOverHandler(object? sender, DragEventArgs e) + { + if (DataContext is IDropTarget dropTarget) + { + dropTarget.DragOver(sender, e); + } + } + + private void DropHandler(object? sender, DragEventArgs e) + { + if (DataContext is IDropTarget dropTarget) + { + dropTarget.Drop(sender, e); + } + } } diff --git a/StabilityMatrix.Avalonia/Views/InferenceTextToImageView.axaml b/StabilityMatrix.Avalonia/Views/InferenceTextToImageView.axaml index 96221bdb..74f75dcd 100644 --- a/StabilityMatrix.Avalonia/Views/InferenceTextToImageView.axaml +++ b/StabilityMatrix.Avalonia/Views/InferenceTextToImageView.axaml @@ -88,7 +88,7 @@ x:DataType="Tool" Id="ConfigTool"> + DataContext="{ReflectionBinding ElementName=Dock, Path=DataContext.StackCardViewModel}"/> @@ -122,7 +122,17 @@ Title="Image Output" x:DataType="Tool" Id="ImageGalleryTool"> - + + + + + +