Browse Source

Add remote upscaler downloads

pull/165/head
Ionite 1 year ago
parent
commit
71e9fe0806
No known key found for this signature in database
  1. 38
      StabilityMatrix.Avalonia/Controls/ComfyUpscalerTemplateSelector.cs
  2. 40
      StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml
  3. 50
      StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml.cs
  4. 25
      StabilityMatrix.Avalonia/Converters/ComfyUpscalerConverter.cs
  5. 8
      StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs
  6. 9
      StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
  7. 54
      StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
  8. 14
      StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
  9. 17
      StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs
  10. 20
      StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
  11. 68
      StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs
  12. 9
      StabilityMatrix.Avalonia/Views/InferencePage.axaml
  13. 79
      StabilityMatrix.Core/Models/Api/Comfy/ComfyUpscaler.cs
  14. 3
      StabilityMatrix.Core/Models/Api/Comfy/ComfyUpscalerType.cs
  15. 23
      StabilityMatrix.Core/Models/RemoteResource.cs
  16. 5
      StabilityMatrix.Core/Services/IModelIndexService.cs
  17. 5
      StabilityMatrix.Core/Services/ModelIndexService.cs

38
StabilityMatrix.Avalonia/Controls/ComfyUpscalerTemplateSelector.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Metadata;
using StabilityMatrix.Core.Models.Api.Comfy;
namespace StabilityMatrix.Avalonia.Controls;
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public class ComfyUpscalerTemplateSelector : IDataTemplate
{
// ReSharper disable once CollectionNeverUpdated.Global
[Content]
public Dictionary<ComfyUpscalerType, IDataTemplate> Templates { get; } = new();
// Check if we can accept the provided data
public bool Match(object? data)
{
return data is ComfyUpscaler;
}
// Build the DataTemplate here
public Control Build(object? data)
{
if (data is not ComfyUpscaler card)
throw new ArgumentException(null, nameof(data));
if (Templates.TryGetValue(card.Type, out var type))
{
return type.Build(card)!;
}
// Fallback to None
return Templates[ComfyUpscalerType.None].Build(card)!;
}
}

40
StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml

@ -7,6 +7,8 @@
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:vmInference="using:StabilityMatrix.Avalonia.ViewModels.Inference"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
x:DataType="vmInference:UpscalerCardViewModel">
<Design.PreviewWith>
<Grid MinWidth="300">
@ -33,13 +35,47 @@
VerticalAlignment="Center"
Text="Upscaler" />
<ui:FAComboBox
Name="UpscalerComboBox"
Grid.Row="0"
Grid.Column="1"
ItemsSource="{Binding ClientManager.Upscalers}"
SelectedItem="{Binding SelectedUpscaler}"
DisplayMemberBinding="{Binding DisplayName}"
Margin="8,0,0,8"
HorizontalAlignment="Stretch"/>
HorizontalAlignment="Stretch">
<ui:FAComboBox.Resources>
<input:StandardUICommand
x:Key="RemoteDownloadCommand"
Command="{Binding RemoteDownloadCommand}"/>
</ui:FAComboBox.Resources>
<ui:FAComboBox.DataTemplates>
<controls:ComfyUpscalerTemplateSelector>
<DataTemplate DataType="comfy:ComfyUpscaler" x:Key="{x:Static comfy:ComfyUpscalerType.DownloadableModel}">
<Grid ColumnDefinitions="*,Auto">
<TextBlock
Text="{Binding DisplayName}"
Foreground="{DynamicResource ThemeGreyColor}"/>
<Button
Grid.Column="1"
Classes="transparent-full"
Margin="8,0,0,0"
Padding="0">
<fluentIcons:SymbolIcon
VerticalAlignment="Center"
FontSize="18"
Foreground="{DynamicResource ThemeGreyColor}"
IsFilled="True"
Symbol="CloudArrowDown" />
</Button>
</Grid>
</DataTemplate>
<DataTemplate DataType="comfy:ComfyUpscaler" x:Key="{x:Static comfy:ComfyUpscalerType.None}">
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</controls:ComfyUpscalerTemplateSelector>
</ui:FAComboBox.DataTemplates>
</ui:FAComboBox>
</Grid>
<!-- Dimensions (Scale) -->
<StackPanel>

50
StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml.cs

@ -1,5 +1,51 @@
using Avalonia.Controls.Primitives;
using System;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Core.Models.Api.Comfy;
namespace StabilityMatrix.Avalonia.Controls;
public class UpscalerCard : TemplatedControl { }
public class UpscalerCard : TemplatedControl
{
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var upscalerComboBox = e.NameScope.Find("UpscalerComboBox") as FAComboBox;
upscalerComboBox!.SelectionChanged += UpscalerComboBox_OnSelectionChanged;
}
private void UpscalerComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 0)
return;
var item = e.AddedItems[0];
if (item is ComfyUpscaler { IsDownloadable: true })
{
// Reset the selection
e.Handled = true;
if (
e.RemovedItems.Count > 0
&& e.RemovedItems[0] is ComfyUpscaler { IsDownloadable: false } removedItem
)
{
(sender as FAComboBox)!.SelectedItem = removedItem;
}
else
{
(sender as FAComboBox)!.SelectedItem = null;
}
// Show dialog to download the model
(DataContext as UpscalerCardViewModel)!.RemoteDownloadCommand
.ExecuteAsync(item)
.SafeFireAndForget();
}
}
}

25
StabilityMatrix.Avalonia/Converters/ComfyUpscalerConverter.cs

@ -0,0 +1,25 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
public class ComfyUpscalerConverter : IValueConverter
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public object? ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture
)
{
throw new NotImplementedException();
}
}

8
StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
@ -27,12 +28,7 @@ public partial class MockInferenceClientManager : ObservableObject, IInferenceCl
public IObservableCollection<ComfyUpscaler> Upscalers { get; } =
new ObservableCollectionExtended<ComfyUpscaler>(
new ComfyUpscaler[]
{
new("nearest-exact", ComfyUpscalerType.Latent),
new("bicubic", ComfyUpscalerType.Latent),
new("ESRGAN-4x", ComfyUpscalerType.ESRGAN)
}
ComfyUpscaler.Defaults.Concat(ComfyUpscaler.DefaultDownloadableModels)
);
public IObservableCollection<ComfyScheduler> Schedulers { get; } =

9
StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
@ -17,6 +18,12 @@ public class MockModelIndexService : IModelIndexService
return Task.CompletedTask;
}
/// <inheritdoc />
public IEnumerable<LocalModelFile> GetFromModelIndex(SharedFolderType types)
{
return Array.Empty<LocalModelFile>();
}
/// <inheritdoc />
public Task<IReadOnlyList<LocalModelFile>> GetModelsOfType(SharedFolderType type)
{

54
StabilityMatrix.Avalonia/Services/InferenceClientManager.cs

@ -71,6 +71,9 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
private readonly SourceCache<ComfyUpscaler, string> latentUpscalersSource = new(p => p.Name);
private readonly SourceCache<ComfyUpscaler, string> downloadableUpscalersSource =
new(p => p.Name);
public IObservableCollection<ComfyUpscaler> Upscalers { get; } =
new ObservableCollectionExtended<ComfyUpscaler>();
@ -107,6 +110,7 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
latentUpscalersSource
.Connect()
.Or(modelUpscalersSource.Connect())
.Or(downloadableUpscalersSource.Connect())
.DeferUntilLoaded()
.Bind(Upscalers)
.Subscribe();
@ -207,36 +211,42 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
private void ResetSharedProperties()
{
// Load local models
modelIndexService
.GetModelsOfType(SharedFolderType.StableDiffusion)
.ContinueWith(task =>
{
modelsSource.EditDiff(
task.Result.Select(HybridModelFile.FromLocal),
HybridModelFile.Comparer
);
})
.SafeFireAndForget();
modelsSource.EditDiff(
modelIndexService
.GetFromModelIndex(SharedFolderType.StableDiffusion)
.Select(HybridModelFile.FromLocal),
HybridModelFile.Comparer
);
// Load local VAE models
modelIndexService
.GetModelsOfType(SharedFolderType.VAE)
.ContinueWith(task =>
{
vaeModelsSource.EditDiff(
task.Result.Select(HybridModelFile.FromLocal),
HybridModelFile.Comparer
);
})
.SafeFireAndForget();
vaeModelsSource.EditDiff(
modelIndexService
.GetFromModelIndex(SharedFolderType.VAE)
.Select(HybridModelFile.FromLocal),
HybridModelFile.Comparer
);
samplersSource.EditDiff(ComfySampler.Defaults, ComfySampler.Comparer);
latentUpscalersSource.EditDiff(ComfyUpscaler.Defaults, ComfyUpscaler.Comparer);
modelUpscalersSource.Clear();
schedulersSource.EditDiff(ComfyScheduler.Defaults, ComfyScheduler.Comparer);
// Load Upscalers
modelUpscalersSource.EditDiff(
modelIndexService
.GetFromModelIndex(
SharedFolderType.ESRGAN | SharedFolderType.RealESRGAN | SharedFolderType.SwinIR
)
.Select(m => new ComfyUpscaler(m.FileName, ComfyUpscalerType.ESRGAN)),
ComfyUpscaler.Comparer
);
// Remote upscalers
var remoteUpscalers = ComfyUpscaler.DefaultDownloadableModels.Where(
u => !modelUpscalersSource.Lookup(u.Name).HasValue
);
downloadableUpscalersSource.EditDiff(remoteUpscalers, ComfyUpscaler.Comparer);
}
/// <inheritdoc />

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

@ -180,6 +180,12 @@ public abstract partial class InferenceGenerationViewModelBase
var filePath in images.Select(image => image.ToFilePath(args.Client.OutputImagesDir!))
)
{
if (!filePath.Exists)
{
Logger.Warn($"Image file {filePath} does not exist");
continue;
}
var bytesWithMetadata = PngDataHelper.AddMetadata(
await filePath.ReadAllBytesAsync(),
args.Parameters!,
@ -201,7 +207,13 @@ public abstract partial class InferenceGenerationViewModelBase
if (outputImages.Count > 1)
{
var loadedImages = outputImages
.Select(i => SKImage.FromEncodedData(i.LocalFile?.Info.OpenRead()))
.Select(i => i.LocalFile)
.Where(f => f is { Exists: true })
.Select(f =>
{
using var stream = f!.Info.OpenRead();
return SKImage.FromEncodedData(stream);
})
.ToImmutableArray();
var grid = ImageProcessor.CreateImageGrid(loadedImages);

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

@ -9,6 +9,7 @@ using Avalonia.Input;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.ViewModels.Inference;
@ -112,6 +113,22 @@ public abstract partial class InferenceTabViewModelBase
}
}
[RelayCommand]
private async Task DebugLoadViewState()
{
var textFields = new TextBoxField[] { new() { Label = "Json Data" } };
var dialog = DialogHelper.CreateTextEntryDialog("Load Dock State", "", textFields);
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary && textFields[0].Text is { } json)
{
LoadViewState(
new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } }
);
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)

20
StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs

@ -70,17 +70,27 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
InstalledPackages = comfyPackages;
// If there are none, use install mode
// If no comfy packages, install mode, otherwise launch mode
if (comfyPackages.Length == 0)
{
IsInstallMode = true;
}
// Otherwise launch mode
else
{
SelectedPackage = this.settingsManager.Settings.ActiveInstalledPackage;
SelectedPackage ??= comfyPackages[0];
IsLaunchMode = true;
// Use active package if its comfy, otherwise use the first comfy type
if (
this.settingsManager.Settings.ActiveInstalledPackage is
{ PackageName: "ComfyUI" } activePackage
)
{
SelectedPackage = activePackage;
}
else
{
SelectedPackage ??= comfyPackages[0];
}
}
}
@ -130,7 +140,7 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
PrimaryButtonCommand = IsInstallMode
? NavigateToInstallCommand
: LaunchSelectedPackageCommand,
PrimaryButtonText = IsInstallMode ? "Install" : "Launch",
PrimaryButtonText = IsInstallMode ? Resources.Action_Install : Resources.Action_Launch,
CloseButtonText = Resources.Action_Close,
DefaultButton = ContentDialogButton.Primary
};

68
StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs

@ -1,33 +1,79 @@
using System.Text.Json.Nodes;
using System;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api.Comfy;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(UpscalerCard))]
public partial class UpscalerCardViewModel : LoadableViewModelBase
{
[ObservableProperty] private double scale = 1;
private readonly INotificationService notificationService;
private readonly ITrackedDownloadService trackedDownloadService;
private readonly ISettingsManager settingsManager;
[ObservableProperty]
private double scale = 1;
[ObservableProperty]
private ComfyUpscaler? selectedUpscaler = ComfyUpscaler.Defaults[0];
[ObservableProperty] private ComfyUpscaler? selectedUpscaler = ComfyUpscaler.Defaults[0];
public IInferenceClientManager ClientManager { get; }
public UpscalerCardViewModel(IInferenceClientManager clientManager)
public UpscalerCardViewModel(
IInferenceClientManager clientManager,
INotificationService notificationService,
ITrackedDownloadService trackedDownloadService,
ISettingsManager settingsManager
)
{
this.notificationService = notificationService;
this.trackedDownloadService = trackedDownloadService;
this.settingsManager = settingsManager;
ClientManager = clientManager;
}
[RelayCommand]
private async Task RemoteDownload(ComfyUpscaler? upscaler)
{
if (upscaler?.DownloadableResource is not { } resource)
return;
var sharedFolderType =
resource.ContextType as SharedFolderType?
?? throw new InvalidOperationException("ContextType is not SharedFolderType");
var modelsDir = new DirectoryPath(settingsManager.ModelsDirectory).JoinDir(
sharedFolderType.GetStringValue()
);
var download = trackedDownloadService.NewDownload(
resource.Url,
modelsDir.JoinFile(upscaler.Value.Name)
);
download.Start();
EventManager.Instance.OnToggleProgressFlyout();
}
/// <inheritdoc />
public override void LoadStateFromJsonObject(JsonObject state)
{
var model = DeserializeModel<UpscalerCardModel>(state);
Scale = model.Scale;
SelectedUpscaler = model.SelectedUpscaler;
}
@ -35,10 +81,8 @@ public partial class UpscalerCardViewModel : LoadableViewModelBase
/// <inheritdoc />
public override JsonObject SaveStateToJsonObject()
{
return SerializeModel(new UpscalerCardModel
{
Scale = Scale,
SelectedUpscaler = SelectedUpscaler
});
return SerializeModel(
new UpscalerCardModel { Scale = Scale, SelectedUpscaler = SelectedUpscaler }
);
}
}

9
StabilityMatrix.Avalonia/Views/InferencePage.axaml

@ -207,7 +207,7 @@
<ui:MenuFlyoutItem
Command="{Binding MenuSaveCommand}"
InputGesture="Ctrl+S"
Text="Save" />
Text="{x:Static lang:Resources.Action_Save}" />
<ui:MenuFlyoutItem
Command="{Binding MenuSaveAsCommand}"
InputGesture="Ctrl+Shift+S"
@ -218,9 +218,14 @@
<ui:MenuFlyoutItem
IsVisible="{Binding SharedState.IsDebugMode}"
Command="{Binding SelectedTab.DebugSaveViewStateCommand}"
Command="{Binding SelectedTab.DebugSaveViewStateCommand, FallbackValue={x:Null}}"
Text="Show Dock State" />
<ui:MenuFlyoutItem
IsVisible="{Binding SharedState.IsDebugMode}"
Command="{Binding SelectedTab.DebugLoadViewStateCommand, FallbackValue={x:Null}}"
Text="Load Dock State" />
</ui:FAMenuFlyout>
</Button.Flyout>
</Button>

79
StabilityMatrix.Core/Models/Api/Comfy/ComfyUpscaler.cs

@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.Comfy;
@ -20,6 +21,15 @@ public readonly record struct ComfyUpscaler(string Name, ComfyUpscalerType Type)
.Select(k => new ComfyUpscaler(k, ComfyUpscalerType.Latent))
.ToImmutableArray();
/// <summary>
/// Downloadable model information.
/// If this is set, <see cref="Type"/> should be <see cref="ComfyUpscalerType.DownloadableModel"/>.
/// </summary>
public RemoteResource? DownloadableResource { get; init; }
[MemberNotNullWhen(true, nameof(DownloadableResource))]
public bool IsDownloadable => DownloadableResource != null;
[JsonIgnore]
public string DisplayType
{
@ -29,6 +39,7 @@ public readonly record struct ComfyUpscaler(string Name, ComfyUpscalerType Type)
{
ComfyUpscalerType.Latent => "Latent",
ComfyUpscalerType.ESRGAN => "ESRGAN",
ComfyUpscalerType.DownloadableModel => "Downloadable",
ComfyUpscalerType.None => "None",
_ => throw new ArgumentOutOfRangeException(nameof(Type), Type, null)
};
@ -45,7 +56,7 @@ public readonly record struct ComfyUpscaler(string Name, ComfyUpscalerType Type)
return ConvertDict.TryGetValue(Name, out var displayName) ? displayName : Name;
}
if (Type == ComfyUpscalerType.ESRGAN)
if (Type is ComfyUpscalerType.ESRGAN or ComfyUpscalerType.DownloadableModel)
{
// Remove file extensions
return Path.GetFileNameWithoutExtension(Name);
@ -60,7 +71,7 @@ public readonly record struct ComfyUpscaler(string Name, ComfyUpscalerType Type)
{
get
{
if (Type == ComfyUpscalerType.ESRGAN)
if (Type != ComfyUpscalerType.Latent)
{
// Remove file extensions
return Path.GetFileNameWithoutExtension(Name);
@ -70,6 +81,70 @@ public readonly record struct ComfyUpscaler(string Name, ComfyUpscalerType Type)
}
}
/// <summary>
/// Static root of huggingface upscalers
/// </summary>
private static Uri HuggingFaceRoot { get; } = new("https://huggingface.co/LykosAI/Upscalers/");
/// <summary>
/// Default remote downloadable models
/// </summary>
public static IReadOnlyList<ComfyUpscaler> DefaultDownloadableModels { get; } =
new[]
{
new ComfyUpscaler("RealESRGAN_x2plus.pth", ComfyUpscalerType.DownloadableModel)
{
DownloadableResource = new RemoteResource(
new Uri(
HuggingFaceRoot,
"/resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x2plus.pth"
),
"49fafd45f8fd7aa8d31ab2a22d14d91b536c34494a5cfe31eb5d89c2fa266abb"
)
{
InfoUrl = new Uri("https://github.com/xinntao/ESRGAN"),
Author = "xinntao",
LicenseType = "Apache 2.0",
LicenseUrl = new Uri(HuggingFaceRoot, "RealESRGAN/LICENSE.txt"),
ContextType = SharedFolderType.RealESRGAN
}
},
new ComfyUpscaler("RealESRGAN_x4plus.pth", ComfyUpscalerType.DownloadableModel)
{
DownloadableResource = new RemoteResource(
new Uri(
HuggingFaceRoot,
"/resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x4plus.pth"
),
"4fa0d38905f75ac06eb49a7951b426670021be3018265fd191d2125df9d682f1"
)
{
InfoUrl = new Uri("https://github.com/xinntao/ESRGAN"),
Author = "xinntao",
LicenseType = "Apache 2.0",
LicenseUrl = new Uri(HuggingFaceRoot, "RealESRGAN/LICENSE.txt"),
ContextType = SharedFolderType.RealESRGAN
}
},
new ComfyUpscaler("RealESRGAN_x4plus_anime_6B.pth", ComfyUpscalerType.DownloadableModel)
{
DownloadableResource = new RemoteResource(
new Uri(
HuggingFaceRoot,
"/resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x4plus_anime_6B.pth"
),
"f872d837d3c90ed2e05227bed711af5671a6fd1c9f7d7e91c911a61f155e99da"
)
{
InfoUrl = new Uri("https://github.com/xinntao/ESRGAN"),
Author = "xinntao",
LicenseType = "Apache 2.0",
LicenseUrl = new Uri(HuggingFaceRoot, "RealESRGAN/LICENSE.txt"),
ContextType = SharedFolderType.RealESRGAN
}
}
};
private sealed class NameTypeEqualityComparer : IEqualityComparer<ComfyUpscaler>
{
public bool Equals(ComfyUpscaler x, ComfyUpscaler y)

3
StabilityMatrix.Core/Models/Api/Comfy/ComfyUpscalerType.cs

@ -7,5 +7,6 @@ public enum ComfyUpscalerType
{
None,
Latent,
ESRGAN
ESRGAN,
DownloadableModel
}

23
StabilityMatrix.Core/Models/RemoteResource.cs

@ -6,24 +6,39 @@
public readonly record struct RemoteResource
{
public Uri Url { get; }
public Uri[]? FallbackUrls { get; }
public string HashSha256 { get; }
/// <summary>
/// Type info, for remote models this is <see cref="SharedFolderType"/> of the model.
/// </summary>
public object? ContextType { get; init; }
public Uri? InfoUrl { get; init; }
public string? LicenseType { get; init; }
public Uri? LicenseUrl { get; init; }
public string? Author { get; init; }
public string? ByAuthor => string.IsNullOrEmpty(Author) ? null : $"by {Author}";
public RemoteResource(Uri url, string hashSha256)
{
Url = url;
HashSha256 = hashSha256;
}
public RemoteResource(Uri[] urls, string hashSha256)
{
if (urls.Length == 0)
{
throw new ArgumentException("Must have at least one url.", nameof(urls));
}
Url = urls[0];
FallbackUrls = urls.Length > 1 ? urls[1..] : null;
HashSha256 = hashSha256;

5
StabilityMatrix.Core/Services/IModelIndexService.cs

@ -12,6 +12,11 @@ public interface IModelIndexService
/// </summary>
Task RefreshIndex();
/// <summary>
/// Get all models of the specified type from the existing (in-memory) index.
/// </summary>
IEnumerable<LocalModelFile> GetFromModelIndex(SharedFolderType types);
/// <summary>
/// Get all models of the specified type from the existing index.
/// </summary>

5
StabilityMatrix.Core/Services/ModelIndexService.cs

@ -38,6 +38,11 @@ public class ModelIndexService : IModelIndexService
await liteDbContext.LocalModelFiles.DeleteAllAsync().ConfigureAwait(false);
}
public IEnumerable<LocalModelFile> GetFromModelIndex(SharedFolderType types)
{
return ModelIndex.Where(kvp => (kvp.Key & types) != 0).SelectMany(kvp => kvp.Value);
}
/// <inheritdoc />
public async Task<IReadOnlyList<LocalModelFile>> GetModelsOfType(SharedFolderType type)
{

Loading…
Cancel
Save