Browse Source

Merge pull request #275 from ionite34/inference-metadata

pull/165/head
Ionite 1 year ago committed by GitHub
parent
commit
3a55b79d4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      StabilityMatrix.Avalonia/Controls/Dock/DockUserControlBase.cs
  2. 27
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  3. 9
      StabilityMatrix.Avalonia/Languages/Resources.resx
  4. 15
      StabilityMatrix.Avalonia/Models/IParametersLoadableState.cs
  5. 2
      StabilityMatrix.Avalonia/Models/Inference/LoadViewStateEventArgs.cs
  6. 5
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  7. 1
      StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
  8. 183
      StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs
  9. 28
      StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
  10. 49
      StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
  11. 20
      StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
  12. 79
      StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
  13. 16
      StabilityMatrix.Avalonia/Views/Inference/InferenceTextToImageView.axaml
  14. 13
      StabilityMatrix.Avalonia/Views/InferencePage.axaml
  15. 70
      StabilityMatrix.Core/Helper/GenerationParametersConverter.cs
  16. 25
      StabilityMatrix.Core/Helper/ImageMetadata.cs
  17. 81
      StabilityMatrix.Core/Models/Api/Comfy/ComfySampler.cs
  18. 25
      StabilityMatrix.Core/Models/Api/Comfy/ComfySamplerScheduler.cs
  19. 6
      StabilityMatrix.Core/Models/Api/Comfy/ComfyScheduler.cs
  20. 46
      StabilityMatrix.Core/Models/GenerationParameters.cs
  21. 3
      StabilityMatrix.sln.DotSettings

45
StabilityMatrix.Avalonia/Controls/Dock/DockUserControlBase.cs

@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Threading; using Avalonia.Threading;
@ -10,6 +11,7 @@ using Dock.Avalonia.Controls;
using Dock.Model; using Dock.Model;
using Dock.Model.Avalonia.Json; using Dock.Model.Avalonia.Json;
using Dock.Model.Core; using Dock.Model.Core;
using Dock.Serializer;
using StabilityMatrix.Avalonia.Models.Inference; using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
@ -21,22 +23,23 @@ namespace StabilityMatrix.Avalonia.Controls.Dock;
/// </summary> /// </summary>
public abstract class DockUserControlBase : DropTargetUserControlBase public abstract class DockUserControlBase : DropTargetUserControlBase
{ {
protected DockControl? BaseDock; private DockControl? baseDock;
protected readonly AvaloniaDockSerializer DockSerializer = new(); private readonly DockSerializer dockSerializer = new(typeof(AvaloniaList<>));
protected readonly DockState DockState = new(); private readonly DockState dockState = new();
/// <inheritdoc /> /// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
BaseDock = baseDock =
this.FindControl<DockControl>("Dock") this.FindControl<DockControl>("Dock")
?? throw new NullReferenceException("DockControl not found"); ?? throw new NullReferenceException("DockControl not found");
if (BaseDock.Layout is { } layout) if (baseDock.Layout is { } layout)
{ {
Dispatcher.UIThread.Post(() => DockState.Save(layout), DispatcherPriority.Background); dockState.Save(layout);
// Dispatcher.UIThread.Post(() => dockState.Save(layout), DispatcherPriority.Background);
} }
} }
@ -81,31 +84,45 @@ public abstract class DockUserControlBase : DropTargetUserControlBase
private void DataContext_OnLoadViewStateRequested(object? sender, LoadViewStateEventArgs args) private void DataContext_OnLoadViewStateRequested(object? sender, LoadViewStateEventArgs args)
{ {
if (args.State.DockLayout is { } layout) if (args.State?.DockLayout is { } layout)
{ {
// Provided
LoadDockLayout(layout); LoadDockLayout(layout);
} }
else
{
// Restore default
RestoreDockLayout();
}
} }
protected void LoadDockLayout(JsonObject data) private void LoadDockLayout(JsonObject data)
{ {
LoadDockLayout(data.ToJsonString()); LoadDockLayout(data.ToJsonString());
} }
protected void LoadDockLayout(string data) private void LoadDockLayout(string data)
{ {
if (BaseDock is null) if (baseDock is null)
return; return;
if (DockSerializer.Deserialize<IDock?>(data) is { } layout) if (dockSerializer.Deserialize<IDock?>(data) is { } layout)
{
baseDock.Layout = layout;
}
}
private void RestoreDockLayout()
{
// TODO: idk this doesn't work
if (baseDock?.Layout != null)
{ {
BaseDock.Layout = layout; dockState.Restore(baseDock.Layout);
DockState.Restore(layout);
} }
} }
protected string? SaveDockLayout() protected string? SaveDockLayout()
{ {
return BaseDock is null ? null : DockSerializer.Serialize(BaseDock.Layout); return baseDock is null ? null : dockSerializer.Serialize(baseDock.Layout);
} }
} }

27
StabilityMatrix.Avalonia/Languages/Resources.Designer.cs generated

@ -257,6 +257,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Open Project....
/// </summary>
public static string Action_OpenProjectEllipsis {
get {
return ResourceManager.GetString("Action_OpenProjectEllipsis", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Open Web UI. /// Looks up a localized string similar to Open Web UI.
/// </summary> /// </summary>
@ -329,6 +338,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Restore Default Layout.
/// </summary>
public static string Action_RestoreDefaultLayout {
get {
return ResourceManager.GetString("Action_RestoreDefaultLayout", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Retry. /// Looks up a localized string similar to Retry.
/// </summary> /// </summary>
@ -347,6 +365,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Save As....
/// </summary>
public static string Action_SaveAsEllipsis {
get {
return ResourceManager.GetString("Action_SaveAsEllipsis", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Search. /// Looks up a localized string similar to Search.
/// </summary> /// </summary>

9
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -669,4 +669,13 @@
<data name="Label_ReleaseNotes" xml:space="preserve"> <data name="Label_ReleaseNotes" xml:space="preserve">
<value>Release Notes</value> <value>Release Notes</value>
</data> </data>
<data name="Action_OpenProjectEllipsis" xml:space="preserve">
<value>Open Project...</value>
</data>
<data name="Action_SaveAsEllipsis" xml:space="preserve">
<value>Save As...</value>
</data>
<data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value>Restore Default Layout</value>
</data>
</root> </root>

15
StabilityMatrix.Avalonia/Models/IParametersLoadableState.cs

@ -0,0 +1,15 @@
using StabilityMatrix.Core.Models;
namespace StabilityMatrix.Avalonia.Models;
public interface IParametersLoadableState
{
void LoadStateFromParameters(GenerationParameters parameters);
GenerationParameters SaveStateToParameters(GenerationParameters parameters);
public GenerationParameters SaveStateToParameters()
{
return SaveStateToParameters(new GenerationParameters());
}
}

2
StabilityMatrix.Avalonia/Models/Inference/LoadViewStateEventArgs.cs

@ -7,5 +7,5 @@ namespace StabilityMatrix.Avalonia.Models.Inference;
/// </summary> /// </summary>
public class LoadViewStateEventArgs : EventArgs public class LoadViewStateEventArgs : EventArgs
{ {
public required ViewState State { get; init; } public ViewState? State { get; init; }
} }

5
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -8,7 +8,7 @@
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationIcon>./Assets/Icon.ico</ApplicationIcon> <ApplicationIcon>./Assets/Icon.ico</ApplicationIcon>
<Version>2.5.0-dev.5</Version> <Version>2.5.0-pre.2</Version>
<InformationalVersion>$(Version)</InformationalVersion> <InformationalVersion>$(Version)</InformationalVersion>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@ -33,8 +33,9 @@
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.0" /> <PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="Dock.Avalonia" Version="11.0.0.1" /> <PackageReference Include="Dock.Avalonia" Version="11.0.0.2" />
<PackageReference Include="Dock.Model.Avalonia" Version="11.0.0.2" /> <PackageReference Include="Dock.Model.Avalonia" Version="11.0.0.2" />
<PackageReference Include="Dock.Serializer" Version="11.0.0.2" />
<PackageReference Include="DynamicData" Version="7.14.2" /> <PackageReference Include="DynamicData" Version="7.14.2" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.4" /> <PackageReference Include="FluentAvaloniaUI" Version="2.0.4" />
<PackageReference Include="FluentIcons.Avalonia" Version="1.1.217" /> <PackageReference Include="FluentIcons.Avalonia" Version="1.1.217" />

1
StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs

@ -60,6 +60,7 @@ public abstract partial class InferenceGenerationViewModelBase
IInferenceClientManager inferenceClientManager, IInferenceClientManager inferenceClientManager,
INotificationService notificationService INotificationService notificationService
) )
: base(notificationService)
{ {
this.notificationService = notificationService; this.notificationService = notificationService;
this.vmFactory = vmFactory; this.vmFactory = vmFactory;

183
StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs

@ -1,18 +1,29 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncAwaitBestPractices; using AsyncAwaitBestPractices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Platform.Storage;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using Microsoft.Extensions.DependencyInjection;
using NLog;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference; using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Inference; using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database; using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.FileInterfaces;
@ -26,6 +37,10 @@ public abstract partial class InferenceTabViewModelBase
IPersistentViewProvider, IPersistentViewProvider,
IDropTarget IDropTarget
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly INotificationService notificationService;
/// <summary> /// <summary>
/// The title of the tab /// The title of the tab
/// </summary> /// </summary>
@ -66,6 +81,8 @@ public abstract partial class InferenceTabViewModelBase
protected void LoadViewState(LoadViewStateEventArgs args) => protected void LoadViewState(LoadViewStateEventArgs args) =>
loadViewStateRequestedEventManager?.RaiseEvent(this, args, nameof(LoadViewStateRequested)); loadViewStateRequestedEventManager?.RaiseEvent(this, args, nameof(LoadViewStateRequested));
protected void ResetViewState() => LoadViewState(new LoadViewStateEventArgs());
private WeakEventManager<SaveViewStateEventArgs>? saveViewStateRequestedEventManager; private WeakEventManager<SaveViewStateEventArgs>? saveViewStateRequestedEventManager;
public event EventHandler<SaveViewStateEventArgs> SaveViewStateRequested public event EventHandler<SaveViewStateEventArgs> SaveViewStateRequested
@ -99,6 +116,23 @@ public abstract partial class InferenceTabViewModelBase
#endregion #endregion
protected InferenceTabViewModelBase(INotificationService notificationService)
{
this.notificationService = notificationService;
}
[RelayCommand]
private void RestoreDefaultViewState()
{
// ResetViewState();
// TODO: Dock reset not working, using this hack for now to get a new view
var navService = App.Services.GetRequiredService<INavigationService>();
navService.NavigateTo<LaunchPageViewModel>(new SuppressNavigationTransitionInfo());
((IPersistentViewProvider)this).AttachedPersistentView = null;
navService.NavigateTo<InferenceViewModel>(new BetterEntranceNavigationTransition());
}
[RelayCommand] [RelayCommand]
private async Task DebugSaveViewState() private async Task DebugSaveViewState()
{ {
@ -144,6 +178,79 @@ public abstract partial class InferenceTabViewModelBase
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary>
/// Loads image and metadata from a file path
/// </summary>
/// <remarks>This is safe to call from non-UI threads</remarks>
/// <param name="filePath">File path</param>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="ApplicationException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private void LoadImageMetadata(FilePath? filePath)
{
if (filePath is not { Exists: true })
{
throw new FileNotFoundException("File does not exist", filePath?.FullPath);
}
var metadata = ImageMetadata.GetAllFileMetadata(filePath);
// Has SMProject metadata
if (metadata.SMProject is not null)
{
var project = JsonSerializer.Deserialize<InferenceProjectDocument>(metadata.SMProject);
// Check project type matches
if (project?.ProjectType.ToViewModelType() == GetType() && project.State is not null)
{
Dispatcher.UIThread.Invoke(() => LoadStateFromJsonObject(project.State));
}
else
{
throw new ApplicationException("Unsupported project type");
}
// Load image
if (this is IImageGalleryComponent imageGalleryComponent)
{
imageGalleryComponent.LoadImagesToGallery(new ImageSource(filePath));
}
}
// Has generic metadata
else if (metadata.Parameters is { } parametersString)
{
if (!GenerationParameters.TryParse(parametersString, out var parameters))
{
throw new ApplicationException("Failed to parse parameters");
}
if (this is IParametersLoadableState paramsLoadableVm)
{
Dispatcher.UIThread.Invoke(
() => paramsLoadableVm.LoadStateFromParameters(parameters)
);
}
else
{
throw new InvalidOperationException(
"Load parameters target does not implement IParametersLoadableState"
);
}
// Load image
if (this is IImageGalleryComponent imageGalleryComponent)
{
Dispatcher.UIThread.Invoke(
() => imageGalleryComponent.LoadImagesToGallery(new ImageSource(filePath))
);
}
}
else
{
throw new ApplicationException("File does not contain any metadata");
}
}
/// <inheritdoc /> /// <inheritdoc />
public void DragOver(object? sender, DragEventArgs e) public void DragOver(object? sender, DragEventArgs e)
{ {
@ -162,10 +269,10 @@ public abstract partial class InferenceTabViewModelBase
if (e.Data.GetDataFormats().Contains(DataFormats.Files)) if (e.Data.GetDataFormats().Contains(DataFormats.Files))
{ {
e.Handled = true; e.Handled = true;
e.DragEffects = DragDropEffects.None;
return; return;
} }
// Other kinds - not supported
e.DragEffects = DragDropEffects.None; e.DragEffects = DragDropEffects.None;
} }
@ -181,30 +288,42 @@ public abstract partial class InferenceTabViewModelBase
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
var metadata = imageFile.ReadMetadata(); try
if (metadata.SMProject is not null)
{ {
var project = JsonSerializer.Deserialize<InferenceProjectDocument>( var metadata = imageFile.ReadMetadata();
metadata.SMProject if (metadata.SMProject is not null)
);
// Check project type matches
if (
project?.ProjectType.ToViewModelType() == GetType()
&& project.State is not null
)
{ {
LoadStateFromJsonObject(project.State); var project = JsonSerializer.Deserialize<InferenceProjectDocument>(
} metadata.SMProject
// Load image
if (this is IImageGalleryComponent imageGalleryComponent)
{
imageGalleryComponent.LoadImagesToGallery(
new ImageSource(imageFile.GlobalFullPath)
); );
// Check project type matches
if (
project?.ProjectType.ToViewModelType() == GetType()
&& project.State is not null
)
{
LoadStateFromJsonObject(project.State);
}
// Load image
if (this is IImageGalleryComponent imageGalleryComponent)
{
imageGalleryComponent.LoadImagesToGallery(
new ImageSource(imageFile.GlobalFullPath)
);
}
} }
} }
catch (Exception ex)
{
Logger.Warn(ex, "Failed to load image from context drop");
notificationService.ShowPersistent(
$"Could not parse image metadata",
$"{imageFile.FileName} - {ex.Message}",
NotificationType.Warning
);
}
}); });
return; return;
@ -214,6 +333,30 @@ public abstract partial class InferenceTabViewModelBase
if (e.Data.GetDataFormats().Contains(DataFormats.Files)) if (e.Data.GetDataFormats().Contains(DataFormats.Files))
{ {
e.Handled = true; e.Handled = true;
if (e.Data.Get(DataFormats.Files) is IEnumerable<IStorageItem> files)
{
if (files.Select(f => f.TryGetLocalPath()).FirstOrDefault() is { } path)
{
var file = new FilePath(path);
Dispatcher.UIThread.Post(() =>
{
try
{
LoadImageMetadata(file);
}
catch (Exception ex)
{
Logger.Warn(ex, "Failed to load image from OS file drop");
notificationService.ShowPersistent(
$"Could not parse image metadata",
$"{file.Name} - {ex.Message}",
NotificationType.Warning
);
}
});
}
}
} }
} }
} }

28
StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs

@ -25,7 +25,9 @@ using InferenceTextToImageView = StabilityMatrix.Avalonia.Views.Inference.Infere
namespace StabilityMatrix.Avalonia.ViewModels.Inference; namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceTextToImageView), persistent: true)] [View(typeof(InferenceTextToImageView), persistent: true)]
public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase public class InferenceTextToImageViewModel
: InferenceGenerationViewModelBase,
IParametersLoadableState
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@ -327,4 +329,28 @@ public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase
await RunGeneration(generationArgs, cancellationToken); await RunGeneration(generationArgs, cancellationToken);
} }
/// <inheritdoc />
public void LoadStateFromParameters(GenerationParameters parameters)
{
PromptCardViewModel.LoadStateFromParameters(parameters);
SamplerCardViewModel.LoadStateFromParameters(parameters);
SeedCardViewModel.Seed = Convert.ToInt64(parameters.Seed);
ModelCardViewModel.LoadStateFromParameters(parameters);
}
/// <inheritdoc />
public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{
parameters = PromptCardViewModel.SaveStateToParameters(parameters);
parameters = SamplerCardViewModel.SaveStateToParameters(parameters);
parameters.Seed = (ulong)SeedCardViewModel.Seed;
parameters = ModelCardViewModel.SaveStateToParameters(parameters);
return parameters;
}
} }

49
StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs

@ -1,7 +1,9 @@
using System.Linq; using System;
using System.Linq;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
@ -10,7 +12,7 @@ using StabilityMatrix.Core.Models;
namespace StabilityMatrix.Avalonia.ViewModels.Inference; namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(ModelCard))] [View(typeof(ModelCard))]
public partial class ModelCardViewModel : LoadableViewModelBase public partial class ModelCardViewModel : LoadableViewModelBase, IParametersLoadableState
{ {
[ObservableProperty] [ObservableProperty]
private HybridModelFile? selectedModel; private HybridModelFile? selectedModel;
@ -71,4 +73,47 @@ public partial class ModelCardViewModel : LoadableViewModelBase
public string? SelectedVaeName { get; init; } public string? SelectedVaeName { get; init; }
public bool IsVaeSelectionEnabled { get; init; } public bool IsVaeSelectionEnabled { get; init; }
} }
/// <inheritdoc />
public void LoadStateFromParameters(GenerationParameters parameters)
{
if (parameters.ModelName is not { } paramsModelName)
return;
var currentModels = ClientManager.Models;
HybridModelFile? model;
// First try hash match
if (parameters.ModelHash is not null)
{
model = currentModels.FirstOrDefault(
m =>
m.Local?.ConnectedModelInfo?.Hashes.SHA256 is { } sha256
&& sha256.StartsWith(
parameters.ModelHash,
StringComparison.InvariantCultureIgnoreCase
)
);
}
else
{
// Name matches
model = currentModels.FirstOrDefault(m => m.FileName.EndsWith(paramsModelName));
model ??= currentModels.FirstOrDefault(
m => m.ShortDisplayName.StartsWith(paramsModelName)
);
}
if (model is not null)
{
SelectedModel = model;
}
}
/// <inheritdoc />
public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{
return parameters with { ModelName = SelectedModel?.FileName };
}
} }

20
StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs

@ -22,12 +22,13 @@ using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Exceptions; using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Inference; namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(PromptCard))] [View(typeof(PromptCard))]
public partial class PromptCardViewModel : LoadableViewModelBase public partial class PromptCardViewModel : LoadableViewModelBase, IParametersLoadableState
{ {
private readonly IModelIndexService modelIndexService; private readonly IModelIndexService modelIndexService;
@ -284,4 +285,21 @@ public partial class PromptCardViewModel : LoadableViewModelBase
PromptDocument.Text = model.Prompt ?? ""; PromptDocument.Text = model.Prompt ?? "";
NegativePromptDocument.Text = model.NegativePrompt ?? ""; NegativePromptDocument.Text = model.NegativePrompt ?? "";
} }
/// <inheritdoc />
public void LoadStateFromParameters(GenerationParameters parameters)
{
PromptDocument.Text = parameters.PositivePrompt ?? "";
NegativePromptDocument.Text = parameters.NegativePrompt ?? "";
}
/// <inheritdoc />
public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{
return parameters with
{
PositivePrompt = PromptDocument.Text,
NegativePrompt = NegativePromptDocument.Text
};
}
} }

79
StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs

@ -1,15 +1,19 @@
using System.Text.Json.Serialization; using System.Linq;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api.Comfy; using StabilityMatrix.Core.Models.Api.Comfy;
namespace StabilityMatrix.Avalonia.ViewModels.Inference; namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(SamplerCard))] [View(typeof(SamplerCard))]
public partial class SamplerCardViewModel : LoadableViewModelBase public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLoadableState
{ {
[ObservableProperty] [ObservableProperty]
private bool isRefinerStepsEnabled; private bool isRefinerStepsEnabled;
@ -61,42 +65,47 @@ public partial class SamplerCardViewModel : LoadableViewModelBase
ClientManager = clientManager; ClientManager = clientManager;
} }
/*/// <inheritdoc /> /// <inheritdoc />
public override void LoadStateFromJsonObject(JsonObject state) public void LoadStateFromParameters(GenerationParameters parameters)
{ {
var model = DeserializeModel<SamplerCardModel>(state); Width = parameters.Width;
Height = parameters.Height;
Steps = model.Steps; Steps = parameters.Steps;
IsDenoiseStrengthEnabled = model.IsDenoiseStrengthEnabled; CfgScale = parameters.CfgScale;
DenoiseStrength = model.DenoiseStrength;
IsCfgScaleEnabled = model.IsCfgScaleEnabled; if (
CfgScale = model.CfgScale; !string.IsNullOrEmpty(parameters.Sampler)
IsDimensionsEnabled = model.IsDimensionsEnabled; && GenerationParametersConverter.TryGetSamplerScheduler(
Width = model.Width; parameters.Sampler,
Height = model.Height; out var samplerScheduler
IsSamplerSelectionEnabled = model.IsSamplerSelectionEnabled; )
SelectedSampler = model.SelectedSampler is null )
? null {
: new ComfySampler(model.SelectedSampler); SelectedSampler = ClientManager.Samplers.FirstOrDefault(
s => s == samplerScheduler.Sampler
);
SelectedScheduler = ClientManager.Schedulers.FirstOrDefault(
s => s == samplerScheduler.Scheduler
);
}
} }
/// <inheritdoc /> /// <inheritdoc />
public override JsonObject SaveStateToJsonObject() public GenerationParameters SaveStateToParameters(GenerationParameters parameters)
{ {
return SerializeModel( var sampler = GenerationParametersConverter.TryGetParameters(
new SamplerCardModel new ComfySamplerScheduler(SelectedSampler ?? default, SelectedScheduler ?? default),
{ out var res
Steps = Steps, )
IsDenoiseStrengthEnabled = IsDenoiseStrengthEnabled, ? res
DenoiseStrength = DenoiseStrength, : null;
IsCfgScaleEnabled = IsCfgScaleEnabled, return parameters with
CfgScale = CfgScale, {
IsDimensionsEnabled = IsDimensionsEnabled, Width = Width,
Width = Width, Height = Height,
Height = Height, Steps = Steps,
IsSamplerSelectionEnabled = IsSamplerSelectionEnabled, CfgScale = CfgScale,
SelectedSampler = SelectedSampler?.Name Sampler = sampler,
} };
); }
}*/
} }

16
StabilityMatrix.Avalonia/Views/Inference/InferenceTextToImageView.axaml

@ -36,7 +36,9 @@
x:Name="MainLayout" x:Name="MainLayout"
Id="MainLayout" Id="MainLayout"
Orientation="Horizontal"> Orientation="Horizontal">
<ProportionalDockSplitter x:Name="Splitter1" Id="Splitter1" />
<!-- Left Pane --> <!-- Left Pane -->
<ToolDock <ToolDock
x:Name="ConfigPane" x:Name="ConfigPane"
@ -55,12 +57,12 @@
</Tool> </Tool>
</ToolDock> </ToolDock>
<ProportionalDockSplitter x:Name="LeftSplitter" Id="LeftSplitter" /> <ProportionalDockSplitter x:Name="Splitter2" Id="Splitter2" />
<!-- Prompt Pane --> <!-- Prompt Pane -->
<ToolDock <ToolDock
x:Name="PromptPane" x:Name="PromptPane"
Alignment="Right" Alignment="Left"
Id="PromptPane" Id="PromptPane"
Proportion="0.25"> Proportion="0.25">
<Tool <Tool
@ -149,7 +151,7 @@
</Tool> </Tool>
</ToolDock> </ToolDock>
<ProportionalDockSplitter x:Name="MiddleRightSplitter" Id="MiddleRightSplitter" /> <ProportionalDockSplitter x:Name="Splitter3" Id="Splitter3" />
<!-- Middle Right Pane --> <!-- Middle Right Pane -->
<ToolDock <ToolDock
@ -191,7 +193,7 @@
</Tool> </Tool>
</ToolDock> </ToolDock>
<ProportionalDockSplitter x:Name="RightSplitter" Id="RightSplitter" /> <ProportionalDockSplitter x:Name="Splitter4" Id="Splitter4" />
<!-- Right Pane --> <!-- Right Pane -->
<ToolDock <ToolDock
@ -208,12 +210,14 @@
<Grid <Grid
x:CompileBindings="False" x:CompileBindings="False"
DataContext="{Binding ElementName=Dock, Path=DataContext}"> DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DockControl}, Path=DataContext}">
<controls:ImageFolderCard <controls:ImageFolderCard
DataContext="{Binding ImageFolderCardViewModel}" /> DataContext="{Binding ImageFolderCardViewModel}" />
</Grid> </Grid>
</Tool> </Tool>
</ToolDock> </ToolDock>
<ProportionalDockSplitter x:Name="Splitter5" Id="Splitter5" />
</ProportionalDock> </ProportionalDock>
</RootDock> </RootDock>

13
StabilityMatrix.Avalonia/Views/InferencePage.axaml

@ -202,16 +202,25 @@
<ui:MenuFlyoutItem <ui:MenuFlyoutItem
Command="{Binding MenuOpenProjectCommand}" Command="{Binding MenuOpenProjectCommand}"
InputGesture="Ctrl+O" InputGesture="Ctrl+O"
Text="Open Project..." /> IconSource="OpenFolder"
Text="{x:Static lang:Resources.Action_OpenProjectEllipsis}" />
<ui:MenuFlyoutSeparator /> <ui:MenuFlyoutSeparator />
<ui:MenuFlyoutItem <ui:MenuFlyoutItem
Command="{Binding MenuSaveCommand}" Command="{Binding MenuSaveCommand}"
InputGesture="Ctrl+S" InputGesture="Ctrl+S"
IconSource="Save"
Text="{x:Static lang:Resources.Action_Save}" /> Text="{x:Static lang:Resources.Action_Save}" />
<ui:MenuFlyoutItem <ui:MenuFlyoutItem
Command="{Binding MenuSaveAsCommand}" Command="{Binding MenuSaveAsCommand}"
InputGesture="Ctrl+Shift+S" InputGesture="Ctrl+Shift+S"
Text="Save As..." /> Text="{x:Static lang:Resources.Action_SaveAsEllipsis}" />
<ui:MenuFlyoutSeparator />
<ui:MenuFlyoutItem
Command="{Binding SelectedTab.RestoreDefaultViewStateCommand, FallbackValue={x:Null}}"
IconSource="CalendarReply"
Text="{x:Static lang:Resources.Action_RestoreDefaultLayout}" />
<!-- Debug mode menus --> <!-- Debug mode menus -->
<ui:MenuFlyoutSeparator IsVisible="{Binding SharedState.IsDebugMode}" /> <ui:MenuFlyoutSeparator IsVisible="{Binding SharedState.IsDebugMode}" />

70
StabilityMatrix.Core/Helper/GenerationParametersConverter.cs

@ -0,0 +1,70 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using StabilityMatrix.Core.Models.Api.Comfy;
namespace StabilityMatrix.Core.Helper;
public static class GenerationParametersConverter
{
private static readonly ImmutableDictionary<
string,
ComfySamplerScheduler
> ParamsToSamplerSchedulers = new Dictionary<string, ComfySamplerScheduler>
{
["DPM++ 2M Karras"] = (ComfySampler.Dpmpp2M, ComfyScheduler.Karras),
["DPM++ SDE Karras"] = (ComfySampler.DpmppSde, ComfyScheduler.Karras),
["DPM++ 2M SDE Exponential"] = (ComfySampler.Dpmpp2MSde, ComfyScheduler.Exponential),
["DPM++ 2M SDE Karras"] = (ComfySampler.Dpmpp2MSde, ComfyScheduler.Karras),
["Euler a"] = (ComfySampler.EulerAncestral, ComfyScheduler.Normal),
["Euler"] = (ComfySampler.Euler, ComfyScheduler.Normal),
["LMS"] = (ComfySampler.LMS, ComfyScheduler.Normal),
["Heun"] = (ComfySampler.Heun, ComfyScheduler.Normal),
["DPM2"] = (ComfySampler.Dpm2, ComfyScheduler.Normal),
["DPM2 Karras"] = (ComfySampler.Dpm2, ComfyScheduler.Karras),
["DPM2 a"] = (ComfySampler.Dpm2Ancestral, ComfyScheduler.Normal),
["DPM2 a Karras"] = (ComfySampler.Dpm2Ancestral, ComfyScheduler.Karras),
["DPM++ 2S a"] = (ComfySampler.Dpmpp2SAncestral, ComfyScheduler.Normal),
["DPM++ 2S a Karras"] = (ComfySampler.Dpmpp2SAncestral, ComfyScheduler.Karras),
["DPM++ 2M"] = (ComfySampler.Dpmpp2M, ComfyScheduler.Normal),
["DPM++ SDE"] = (ComfySampler.DpmppSde, ComfyScheduler.Normal),
["DPM++ 2M SDE"] = (ComfySampler.Dpmpp2MSde, ComfyScheduler.Normal),
["DPM++ 3M SDE"] = (ComfySampler.Dpmpp3MSde, ComfyScheduler.Normal),
["DPM++ 3M SDE Karras"] = (ComfySampler.Dpmpp3MSde, ComfyScheduler.Karras),
["DPM++ 3M SDE Exponential"] = (ComfySampler.Dpmpp3MSde, ComfyScheduler.Exponential),
["DPM fast"] = (ComfySampler.DpmFast, ComfyScheduler.Normal),
["DPM adaptive"] = (ComfySampler.DpmAdaptive, ComfyScheduler.Normal),
["LMS Karras"] = (ComfySampler.LMS, ComfyScheduler.Karras),
["DDIM"] = (ComfySampler.DDIM, ComfyScheduler.Normal),
["UniPC"] = (ComfySampler.UniPC, ComfyScheduler.Normal),
}.ToImmutableDictionary();
private static readonly ImmutableDictionary<
ComfySamplerScheduler,
string
> SamplerSchedulersToParams = ParamsToSamplerSchedulers.ToImmutableDictionary(
x => x.Value,
x => x.Key
);
/// <summary>
/// Converts a parameters-type string to a <see cref="ComfySamplerScheduler"/>.
/// </summary>
public static bool TryGetSamplerScheduler(
string parameters,
out ComfySamplerScheduler samplerScheduler
)
{
return ParamsToSamplerSchedulers.TryGetValue(parameters, out samplerScheduler);
}
/// <summary>
/// Converts a <see cref="ComfySamplerScheduler"/> to a parameters-type string.
/// </summary>
public static bool TryGetParameters(
ComfySamplerScheduler samplerScheduler,
[NotNullWhen(true)] out string? parameters
)
{
return SamplerSchedulersToParams.TryGetValue(samplerScheduler, out parameters);
}
}

25
StabilityMatrix.Core/Helper/ImageMetadata.cs

@ -65,6 +65,29 @@ public class ImageMetadata
return new System.Drawing.Size(imageWidth, imageHeight); return new System.Drawing.Size(imageWidth, imageHeight);
} }
public static (
string? Parameters,
string? ParametersJson,
string? SMProject,
string? ComfyNodes
) GetAllFileMetadata(FilePath filePath)
{
using var stream = filePath.Info.OpenRead();
using var reader = new BinaryReader(stream);
var parameters = ReadTextChunk(reader, "parameters");
var parametersJson = ReadTextChunk(reader, "parameters-json");
var smProject = ReadTextChunk(reader, "smproj");
var comfyNodes = ReadTextChunk(reader, "prompt");
return (
string.IsNullOrEmpty(parameters) ? null : parameters,
string.IsNullOrEmpty(parametersJson) ? null : parametersJson,
string.IsNullOrEmpty(smProject) ? null : smProject,
string.IsNullOrEmpty(comfyNodes) ? null : comfyNodes
);
}
public IEnumerable<Tag>? GetTextualData() public IEnumerable<Tag>? GetTextualData()
{ {
// Get the PNG-tEXt directory // Get the PNG-tEXt directory
@ -118,7 +141,7 @@ public class ImageMetadata
{ {
// skip to end of png header stuff // skip to end of png header stuff
byteStream.BaseStream.Position = 0x21; byteStream.BaseStream.Position = 0x21;
while (byteStream.BaseStream.Position < byteStream.BaseStream.Length) while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)
{ {
var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).Reverse().ToArray()); var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).Reverse().ToArray());
var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4)); var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4));

81
StabilityMatrix.Core/Models/Api/Comfy/ComfySampler.cs

@ -1,39 +1,74 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace StabilityMatrix.Core.Models.Api.Comfy; namespace StabilityMatrix.Core.Models.Api.Comfy;
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public readonly record struct ComfySampler(string Name) public readonly record struct ComfySampler(string Name)
{ {
private static Dictionary<string, string> ConvertDict { get; } = public static ComfySampler Euler { get; } = new("euler");
public static ComfySampler EulerAncestral { get; } = new("euler_ancestral");
public static ComfySampler Heun { get; } = new("heun");
public static ComfySampler Dpm2 { get; } = new("dpm_2");
public static ComfySampler Dpm2Ancestral { get; } = new("dpm_2_ancestral");
public static ComfySampler LMS { get; } = new("lms");
public static ComfySampler DpmFast { get; } = new("dpm_fast");
public static ComfySampler DpmAdaptive { get; } = new("dpm_adaptive");
public static ComfySampler Dpmpp2SAncestral { get; } = new("dpmpp_2s_ancestral");
public static ComfySampler DpmppSde { get; } = new("dpmpp_sde");
public static ComfySampler DpmppSdeGpu { get; } = new("dpmpp_sde_gpu");
public static ComfySampler Dpmpp2M { get; } = new("dpmpp_2m");
public static ComfySampler Dpmpp2MSde { get; } = new("dpmpp_2m_sde");
public static ComfySampler Dpmpp2MSdeGpu { get; } = new("dpmpp_2m_sde_gpu");
public static ComfySampler Dpmpp3M { get; } = new("dpmpp_3m");
public static ComfySampler Dpmpp3MSde { get; } = new("dpmpp_3m_sde");
public static ComfySampler Dpmpp3MSdeGpu { get; } = new("dpmpp_3m_sde_gpu");
public static ComfySampler DDIM { get; } = new("ddim");
public static ComfySampler UniPC { get; } = new("uni_pc");
public static ComfySampler UniPCBh2 { get; } = new("uni_pc_bh2");
private static Dictionary<ComfySampler, string> ConvertDict { get; } =
new() new()
{ {
["euler"] = "Euler", [Euler] = "Euler",
["euler_ancestral"] = "Euler Ancestral", [EulerAncestral] = "Euler Ancestral",
["heun"] = "Heun", [Heun] = "Heun",
["dpm_2"] = "DPM 2", [Dpm2] = "DPM 2",
["dpm_2_ancestral"] = "DPM 2 Ancestral", [Dpm2Ancestral] = "DPM 2 Ancestral",
["lms"] = "LMS", [LMS] = "LMS",
["dpm_fast"] = "DPM Fast", [DpmFast] = "DPM Fast",
["dpm_adaptive"] = "DPM Adaptive", [DpmAdaptive] = "DPM Adaptive",
["dpmpp_2s_ancestral"] = "DPM++ 2S Ancestral", [Dpmpp2SAncestral] = "DPM++ 2S Ancestral",
["dpmpp_sde"] = "DPM++ SDE", [DpmppSde] = "DPM++ SDE",
["dpmpp_sde_gpu"] = "DPM++ SDE GPU", [DpmppSdeGpu] = "DPM++ SDE GPU",
["dpmpp_2m"] = "DPM++ 2M", [Dpmpp2M] = "DPM++ 2M",
["dpmpp_2m_sde"] = "DPM++ 2M SDE", [Dpmpp2MSde] = "DPM++ 2M SDE",
["dpmpp_2m_sde_gpu"] = "DPM++ 2M SDE GPU", [Dpmpp2MSdeGpu] = "DPM++ 2M SDE GPU",
["dpmpp_3m"] = "DPM++ 3M", [Dpmpp3M] = "DPM++ 3M",
["dpmpp_3m_sde"] = "DPM++ 3M SDE", [Dpmpp3MSde] = "DPM++ 3M SDE",
["dpmpp_3m_sde_gpu"] = "DPM++ 3M SDE GPU", [Dpmpp3MSdeGpu] = "DPM++ 3M SDE GPU",
["ddim"] = "DDIM", [DDIM] = "DDIM",
["uni_pc"] = "UniPC", [UniPC] = "UniPC",
["uni_pc_bh2"] = "UniPC BH2" [UniPCBh2] = "UniPC BH2"
}; };
public static IReadOnlyList<ComfySampler> Defaults { get; } = public static IReadOnlyList<ComfySampler> Defaults { get; } =
ConvertDict.Keys.Select(k => new ComfySampler(k)).ToImmutableArray(); ConvertDict.Keys.ToImmutableArray();
public string DisplayName => public string DisplayName =>
ConvertDict.TryGetValue(Name, out var displayName) ? displayName : Name; ConvertDict.TryGetValue(this, out var displayName) ? displayName : Name;
/// <inheritdoc />
public bool Equals(ComfySampler other)
{
return Name == other.Name;
}
/// <inheritdoc />
public override int GetHashCode()
{
return Name.GetHashCode();
}
private sealed class NameEqualityComparer : IEqualityComparer<ComfySampler> private sealed class NameEqualityComparer : IEqualityComparer<ComfySampler>
{ {

25
StabilityMatrix.Core/Models/Api/Comfy/ComfySamplerScheduler.cs

@ -0,0 +1,25 @@
namespace StabilityMatrix.Core.Models.Api.Comfy;
/// <summary>
/// Pair of <see cref="ComfySampler"/> and <see cref="ComfyScheduler"/>
/// </summary>
public readonly record struct ComfySamplerScheduler(ComfySampler Sampler, ComfyScheduler Scheduler)
{
/// <inheritdoc />
public bool Equals(ComfySamplerScheduler other)
{
return Sampler.Equals(other.Sampler) && Scheduler.Equals(other.Scheduler);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(Sampler, Scheduler);
}
// Implicit conversion from (ComfySampler, ComfyScheduler)
public static implicit operator ComfySamplerScheduler((ComfySampler, ComfyScheduler) tuple)
{
return new ComfySamplerScheduler(tuple.Item1, tuple.Item2);
}
}

6
StabilityMatrix.Core/Models/Api/Comfy/ComfyScheduler.cs

@ -4,10 +4,14 @@ namespace StabilityMatrix.Core.Models.Api.Comfy;
public readonly record struct ComfyScheduler(string Name) public readonly record struct ComfyScheduler(string Name)
{ {
public static ComfyScheduler Normal { get; } = new("normal");
public static ComfyScheduler Karras { get; } = new("karras");
public static ComfyScheduler Exponential { get; } = new("exponential");
private static Dictionary<string, string> ConvertDict { get; } = private static Dictionary<string, string> ConvertDict { get; } =
new() new()
{ {
["normal"] = "Normal", [Normal.Name] = "Normal",
["karras"] = "Karras", ["karras"] = "Karras",
["exponential"] = "Exponential", ["exponential"] = "Exponential",
["sgm_uniform"] = "SGM Uniform", ["sgm_uniform"] = "SGM Uniform",

46
StabilityMatrix.Core/Models/GenerationParameters.cs

@ -1,11 +1,12 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using StabilityMatrix.Core.Models.Api.Comfy;
namespace StabilityMatrix.Core.Models; namespace StabilityMatrix.Core.Models;
[JsonSerializable(typeof(GenerationParameters))] [JsonSerializable(typeof(GenerationParameters))]
public partial class GenerationParameters public partial record GenerationParameters
{ {
public string? PositivePrompt { get; set; } public string? PositivePrompt { get; set; }
public string? NegativePrompt { get; set; } public string? NegativePrompt { get; set; }
@ -78,6 +79,8 @@ public partial class GenerationParameters
Sampler = match.Groups["Sampler"].Value, Sampler = match.Groups["Sampler"].Value,
CfgScale = double.Parse(match.Groups["CfgScale"].Value), CfgScale = double.Parse(match.Groups["CfgScale"].Value),
Seed = ulong.Parse(match.Groups["Seed"].Value), Seed = ulong.Parse(match.Groups["Seed"].Value),
Height = int.Parse(match.Groups["Height"].Value),
Width = int.Parse(match.Groups["Width"].Value),
ModelHash = match.Groups["ModelHash"].Value, ModelHash = match.Groups["ModelHash"].Value,
ModelName = match.Groups["ModelName"].Value, ModelName = match.Groups["ModelName"].Value,
}; };
@ -85,8 +88,47 @@ public partial class GenerationParameters
return true; return true;
} }
/// <summary>
/// Converts current <see cref="Sampler"/> string to <see cref="ComfySampler"/> and <see cref="ComfyScheduler"/>.
/// </summary>
/// <returns></returns>
public (ComfySampler sampler, ComfyScheduler scheduler)? GetComfySamplers()
{
if (Sampler is not { } source)
return null;
var scheduler = source switch
{
_ when source.Contains("Karras") => ComfyScheduler.Karras,
_ when source.Contains("Exponential") => ComfyScheduler.Exponential,
_ => ComfyScheduler.Normal,
};
var sampler = source switch
{
"LMS" => ComfySampler.LMS,
"DDIM" => ComfySampler.DDIM,
"UniPC" => ComfySampler.UniPC,
"DPM fast" => ComfySampler.DpmFast,
"DPM adaptive" => ComfySampler.DpmAdaptive,
"Heun" => ComfySampler.Heun,
_ when source.StartsWith("DPM2 a") => ComfySampler.Dpm2Ancestral,
_ when source.StartsWith("DPM2") => ComfySampler.Dpm2,
_ when source.StartsWith("DPM++ 2M SDE") => ComfySampler.Dpmpp2MSde,
_ when source.StartsWith("DPM++ 2M") => ComfySampler.Dpmpp2M,
_ when source.StartsWith("DPM++ 3M SDE") => ComfySampler.Dpmpp3MSde,
_ when source.StartsWith("DPM++ 3M") => ComfySampler.Dpmpp3M,
_ when source.StartsWith("DPM++ SDE") => ComfySampler.DpmppSde,
_ when source.StartsWith("DPM++ 2S a") => ComfySampler.Dpmpp2SAncestral,
_ => default
};
return (sampler, scheduler);
}
// Example: Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 7, Seed: 2216407431, Size: 640x896, Model hash: eb2h052f91, Model: anime_v1
[GeneratedRegex( [GeneratedRegex(
"""^Steps: (?<Steps>\d+), Sampler: (?<Sampler>.+?), CFG scale: (?<CfgScale>\d+(\.\d+)?), Seed: (?<Seed>\d+), Size: \d+x\d+, Model hash: (?<ModelHash>.+?), Model: (?<ModelName>.+)$""" """^Steps: (?<Steps>\d+), Sampler: (?<Sampler>.+?), CFG scale: (?<CfgScale>\d+(\.\d+)?), Seed: (?<Seed>\d+), Size: (?<Width>\d+)x(?<Height>\d+), Model hash: (?<ModelHash>.+?), Model: (?<ModelName>.+)$"""
)] )]
private static partial Regex ParseLastLineRegex(); private static partial Regex ParseLastLineRegex();
} }

3
StabilityMatrix.sln.DotSettings

@ -1,8 +1,11 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DDIM/@EntryIndexedValue">DDIM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EOF/@EntryIndexedValue">EOF</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EOF/@EntryIndexedValue">EOF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ESRGAN/@EntryIndexedValue">ESRGAN</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ESRGAN/@EntryIndexedValue">ESRGAN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LMS/@EntryIndexedValue">LMS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LRU/@EntryIndexedValue">LRU</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LRU/@EntryIndexedValue">LRU</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PC/@EntryIndexedValue">PC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/@KeyIndexDefined">True</s:Boolean> <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=1A64B45C2359DD448CF8A25D4A7469F9/@KeyIndexDefined">True</s:Boolean> <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=1A64B45C2359DD448CF8A25D4A7469F9/@KeyIndexDefined">True</s:Boolean>

Loading…
Cancel
Save