Browse Source

Merge branch 'dev' into inference-modules

# Conflicts:
#	StabilityMatrix.Core/Converters/Json/DefaultUnknownEnumConverter.cs
pull/333/head
Ionite 12 months ago
parent
commit
b356460b79
No known key found for this signature in database
  1. 16
      CHANGELOG.md
  2. 2
      StabilityMatrix.Avalonia/App.axaml.cs
  3. 14
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  4. 19
      StabilityMatrix.Avalonia/Helpers/IOCommands.cs
  5. 9
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  6. 3
      StabilityMatrix.Avalonia/Languages/Resources.resx
  7. 20
      StabilityMatrix.Avalonia/ViewModels/Base/ViewModelBase.cs
  8. 10
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
  9. 4
      StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs
  10. 14
      StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
  11. 12
      StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
  12. 49
      StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml
  13. 9
      StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
  14. 3
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
  15. 157
      StabilityMatrix.Core/Converters/Json/DefaultUnknownEnumConverter.cs
  16. 30
      StabilityMatrix.Core/Helper/ArchiveHelper.cs
  17. 11
      StabilityMatrix.Core/Models/Api/CivitCreator.cs
  18. 9
      StabilityMatrix.Core/Models/Api/CivitFileType.cs
  19. 6
      StabilityMatrix.Core/Models/Api/CivitModelFormat.cs
  20. 14
      StabilityMatrix.Core/Models/Api/CivitModelType.cs
  21. 2
      StabilityMatrix.Core/Models/Packages/A3WebUI.cs
  22. 2
      StabilityMatrix.Core/Models/Packages/FocusControlNet.cs
  23. 72
      StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
  24. 1
      StabilityMatrix.Core/Models/Settings/TeachingTip.cs
  25. 3
      StabilityMatrix.Core/Models/SharedFolderType.cs
  26. 66
      StabilityMatrix.Core/Processes/ProcessRunner.cs
  27. 81
      StabilityMatrix.Tests/Core/DefaultUnknownEnumConverterTests.cs

16
CHANGELOG.md

@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
## v2.7.0-dev.3
### Added
- New package: [RuinedFooocus](https://github.com/runew0lf/RuinedFooocus)
#### Model Browser
- Right clicking anywhere on the model card will open the same menu as the three-dots button
- New model downloads will save trigger words in metadata, if available
- Model author username and avatar display, with clickable link to their profile
#### Checkpoints Page
- Added "Copy Trigger Words" option to the three-dots menu on the Checkpoints page (when data is available)
- Added trigger words on checkpoint card and tooltip
@ -19,6 +21,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Animated zoom effect on hovering over model images
#### Checkpoints Page
- Rearranged top row layout to use CommandBar
### Fixed
- Improved startup time and window load time after exiting dialogs
## v2.7.0-dev.2
### Added
@ -50,6 +54,18 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
## Changed
- Model Browser page has been redesigned, featuring more information like rating and download counts
## v2.6.6
### Fixed
- Fixed error when receiving unknown model format values from the Model Browser
## v2.6.5
### Fixed
- Fixed process errors when installing or updating Pip packages using the Python packages dialog
## v2.6.4
### Fixed
- Fixed errors preventing Model Browser from finding results with certain search queries
## v2.6.3
### Fixed
- Fixed InvalidOperationException during prerequisite installs on certain platforms where process name and duration reporting are not supported

2
StabilityMatrix.Avalonia/App.axaml.cs

@ -471,6 +471,8 @@ public sealed class App : Application
};
jsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter());
jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitFileType>());
jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelType>());
jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelFormat>());
jsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
);

14
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -311,7 +311,12 @@ public static class DesignData
Stats = new CivitModelStats { Rating = 3.5, RatingCount = 24 },
ModelVersions = [
new() { Name = "v1.2.2-Inpainting" }
]
],
Creator = new CivitCreator
{
Image = "https://gravatar.com/avatar/fe74084ae8a081dc2283f5bde4736756ad?f=y&d=retro",
Username = "creator-1"
}
};
}),
dialogFactory.Get<CheckpointBrowserCardViewModel>(vm =>
@ -335,7 +340,12 @@ public static class DesignData
}
}
}
]
],
Creator = new CivitCreator
{
Image = "https://gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?f=y&d=retro",
Username = "creator-2"
}
};
})
};

19
StabilityMatrix.Avalonia/Helpers/IOCommands.cs

@ -0,0 +1,19 @@
using CommunityToolkit.Mvvm.Input;
using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Avalonia.Helpers;
public static class IOCommands
{
public static RelayCommand<string?> OpenUrlCommand { get; } =
new(
url =>
{
if (string.IsNullOrWhiteSpace(url))
return;
ProcessRunner.OpenUrl(url);
},
url => !string.IsNullOrWhiteSpace(url)
);
}

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

@ -2075,6 +2075,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here.
/// </summary>
public static string TeachingTip_MoreCheckpointCategories {
get {
return ResourceManager.GetString("TeachingTip_MoreCheckpointCategories", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The app will relaunch after updating.
/// </summary>

3
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -822,4 +822,7 @@
<data name="Label_TriggerWords" xml:space="preserve">
<value>Trigger words:</value>
</data>
<data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve">
<value>Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here</value>
</data>
</root>

20
StabilityMatrix.Avalonia/ViewModels/Base/ViewModelBase.cs

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using JetBrains.Annotations;
using CommunityToolkit.Mvvm.Input;
@ -41,7 +42,10 @@ public partial class ViewModelBase : ObservableValidator, IRemovableListItem
if (!ViewModelState.HasFlag(ViewModelState.InitialLoaded))
{
ViewModelState |= ViewModelState.InitialLoaded;
OnInitialLoaded();
Dispatcher.UIThread.InvokeAsync(OnInitialLoadedAsync).SafeFireAndForget();
}
}
@ -56,10 +60,13 @@ public partial class ViewModelBase : ObservableValidator, IRemovableListItem
/// Runs on the UI thread via Dispatcher.UIThread.InvokeAsync.
/// The view loading will not wait for this to complete.
/// </summary>
public virtual Task OnLoadedAsync()
{
return Task.CompletedTask;
}
public virtual Task OnLoadedAsync() => Task.CompletedTask;
/// <summary>
/// Called the first time the view's LoadedEvent is fired.
/// Sets the <see cref="ViewModelState.InitialLoaded"/> flag.
/// </summary>
protected virtual Task OnInitialLoadedAsync() => Task.CompletedTask;
/// <summary>
/// Called when the view's UnloadedEvent is fired.
@ -71,8 +78,5 @@ public partial class ViewModelBase : ObservableValidator, IRemovableListItem
/// Runs on the UI thread via Dispatcher.UIThread.InvokeAsync.
/// The view loading will not wait for this to complete.
/// </summary>
public virtual Task OnUnloadedAsync()
{
return Task.CompletedTask;
}
public virtual Task OnUnloadedAsync() => Task.CompletedTask;
}

10
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs

@ -470,12 +470,14 @@ public partial class CheckpointBrowserViewModel : PageViewModelBase
}
// See if query is cached
var cachedQuery = await liteDbContext.CivitModelQueryCache
.IncludeAll()
.FindByIdAsync(ObjectHash.GetMd5Guid(modelRequest));
var cachedQueryResult = await notificationService.TryAsync(
liteDbContext.CivitModelQueryCache
.IncludeAll()
.FindByIdAsync(ObjectHash.GetMd5Guid(modelRequest))
);
// If cached, update model cards
if (cachedQuery is not null)
if (cachedQueryResult.Result is { } cachedQuery)
{
var elapsed = timer.Elapsed;
Logger.Debug(

4
StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs

@ -90,8 +90,8 @@ public partial class CheckpointFolder : ViewModelBase
public string TitleWithFilesCount =>
CheckpointFiles.Any() || SubFolders.Any(f => f.CheckpointFiles.Any())
? $"{Title} ({CheckpointFiles.Count + SubFolders.Sum(folder => folder.CheckpointFiles.Count)})"
: Title;
? $"{FolderType.GetDescription() ?? FolderType.GetStringValue()} ({CheckpointFiles.Count + SubFolders.Sum(folder => folder.CheckpointFiles.Count)})"
: FolderType.GetDescription() ?? FolderType.GetStringValue();
public ProgressViewModel Progress { get; } = new();

14
StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs

@ -20,6 +20,7 @@ using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;
namespace StabilityMatrix.Avalonia.ViewModels;
@ -55,6 +56,9 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
[ObservableProperty]
private string searchFilter = string.Empty;
[ObservableProperty]
private bool isCategoryTipOpen;
partial void OnIsImportAsConnectedChanged(bool value)
{
if (
@ -114,6 +118,16 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
if (Design.IsDesignMode)
return;
if (
!settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.CheckpointCategoriesTip)
)
{
IsCategoryTipOpen = true;
settingsManager.Transaction(
s => s.SeenTeachingTips.Add(TeachingTip.CheckpointCategoriesTip)
);
}
IsLoading = CheckpointFolders.Count == 0;
IsIndexing = CheckpointFolders.Count > 0;
// GetStuff();

12
StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs

@ -78,7 +78,7 @@ public partial class MainWindowViewModel : ViewModelBase
SelectedCategory ??= Pages.FirstOrDefault();
}
public override async Task OnLoadedAsync()
protected override async Task OnInitialLoadedAsync()
{
await base.OnLoadedAsync();
@ -102,16 +102,20 @@ public partial class MainWindowViewModel : ViewModelBase
// Index checkpoints if we dont have
Task.Run(() => settingsManager.IndexCheckpoints()).SafeFireAndForget();
if (!App.IsHeadlessMode)
// Disable preload for now, might be causing https://github.com/LykosAI/StabilityMatrix/issues/249
/*if (!App.IsHeadlessMode)
{
PreloadPages();
}
}*/
Program.StartupTimer.Stop();
var startupTime = CodeTimer.FormatTime(Program.StartupTimer.Elapsed);
Logger.Info($"App started ({startupTime})");
if (Program.Args.DebugOneClickInstall || !settingsManager.Settings.InstalledPackages.Any())
if (
Program.Args.DebugOneClickInstall
|| settingsManager.Settings.InstalledPackages.Count == 0
)
{
var viewModel = dialogFactory.Get<OneClickInstallViewModel>();
var dialog = new BetterContentDialog

49
StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml

@ -14,6 +14,7 @@
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
d:DataContext="{x:Static designData:DesignData.CheckpointBrowserViewModel}"
d:DesignHeight="700"
d:DesignWidth="800"
@ -137,7 +138,49 @@
</Grid>
</Button>
</StackPanel>
<!-- Username pill card -->
<Border
BoxShadow="inset 1.2 0 80 1.8 #66000000"
CornerRadius="16"
Margin="4"
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom">
<Border.Resources>
<DropShadowEffect
x:Key="TextDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.9"/>
<DropShadowEffect
x:Key="ImageDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.2"/>
</Border.Resources>
<Button
Command="{x:Static helpers:IOCommands.OpenUrlCommand}"
CommandParameter="{Binding CivitModel.Creator.ProfileUrl}"
CornerRadius="16"
Classes="transparent"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<controls:BetterAdvancedImage
Width="22"
Height="22"
Effect="{StaticResource ImageDropShadowEffect}"
CornerRadius="11"
RenderOptions.BitmapInterpolationMode="HighQuality"
IsVisible="{Binding CivitModel.Creator.Image, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Source="{Binding CivitModel.Creator.Image}"/>
<TextBlock
VerticalAlignment="Center"
Effect="{StaticResource TextDropShadowEffect}"
Text="{Binding CivitModel.Creator.Username}"/>
</StackPanel>
</Button>
</Border>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<controls:Card
@ -406,7 +449,9 @@
Grid.Row="1"
Margin="8,0,8,0"
ScrollChanged="ScrollViewer_OnScrollChanged">
<ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}" ItemsSource="{Binding ModelCards}">
<ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}"
HorizontalAlignment="Center"
ItemsSource="{Binding ModelCards}">
<ItemsRepeater.Layout>
<UniformGridLayout Orientation="Horizontal" />
</ItemsRepeater.Layout>

9
StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml

@ -433,6 +433,7 @@
</TextBox.InnerRightContent>
</TextBox>
<DropDownButton
x:Name="CategoriesDropdown"
Content="{x:Static lang:Resources.Label_Categories}"
Margin="8,0"
VerticalAlignment="Center"
@ -487,7 +488,13 @@
</ui:CommandBarToggleButton>
</ui:CommandBar.SecondaryCommands>
</ui:CommandBar>
<ui:TeachingTip Grid.Row="0" Grid.Column="0" Name="TeachingTip1"
Target="{Binding #CategoriesDropdown}"
Title="{x:Static lang:Resources.TeachingTip_MoreCheckpointCategories}"
PreferredPlacement="Bottom"
IsOpen="{Binding IsCategoryTipOpen}" />
<StackPanel
IsVisible="False"
Grid.Column="1"

3
StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

@ -32,6 +32,7 @@ using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Update;
using StabilityMatrix.Core.Processes;
@ -183,7 +184,7 @@ public partial class MainWindow : AppWindowBase
var tip = this.FindControl<TeachingTip>("UpdateAvailableTeachingTip")!;
tip.Target = target;
tip.Subtitle = $"{Compat.AppVersion} -> {updateInfo.Version}";
tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}";
tip.IsOpen = true;
}
});

157
StabilityMatrix.Core/Converters/Json/DefaultUnknownEnumConverter.cs

@ -1,72 +1,110 @@
using System.Reflection;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
using StabilityMatrix.Core.Extensions;
namespace StabilityMatrix.Core.Converters.Json;
public class DefaultUnknownEnumConverter<T> : JsonConverter<T>
public class DefaultUnknownEnumConverter<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T
> : JsonConverter<T>
where T : Enum
{
// Get EnumMember attribute value
private Dictionary<string, T>? _enumMemberValues;
private IReadOnlyDictionary<string, T> EnumMemberValues =>
_enumMemberValues ??= typeof(T)
.GetFields()
.Where(field => field.IsStatic)
.Select(
field =>
new
{
Field = field,
Attribute = field
.GetCustomAttributes<EnumMemberAttribute>(false)
.FirstOrDefault()
}
)
.Where(field => field.Attribute != null)
.ToDictionary(
field => field.Attribute!.Value!.ToString(),
field => (T)field.Field.GetValue(null)!
);
/// <summary>
/// Lazy initialization for <see cref="EnumMemberValues"/>.
/// </summary>
private readonly Lazy<Dictionary<string, T>> _enumMemberValuesLazy =
new(
() =>
typeof(T)
.GetFields()
.Where(field => field.IsStatic)
.Select(
field =>
new
{
FieldName = field.Name,
FieldValue = (T)field.GetValue(null)!,
EnumMemberValue = field
.GetCustomAttributes<EnumMemberAttribute>(false)
.FirstOrDefault()
?.Value?.ToString()
}
)
.ToDictionary(x => x.EnumMemberValue ?? x.FieldName, x => x.FieldValue)
);
/// <summary>
/// Gets a dictionary of enum member values, keyed by the EnumMember attribute value, or the field name if no EnumMember attribute is present.
/// </summary>
private Dictionary<string, T> EnumMemberValues => _enumMemberValuesLazy.Value;
/// <summary>
/// Lazy initialization for <see cref="EnumMemberNames"/>.
/// </summary>
private readonly Lazy<Dictionary<T, string>> _enumMemberNamesLazy;
/// <summary>
/// Gets a dictionary of enum member names, keyed by the enum member value.
/// </summary>
private Dictionary<T, string> EnumMemberNames => _enumMemberNamesLazy.Value;
/// <summary>
/// Gets the value of the "Unknown" enum member, or the 0 value if no "Unknown" member is present.
/// </summary>
private T UnknownValue =>
EnumMemberValues.TryGetValue("Unknown", out var res) ? res : (T)Enum.ToObject(typeof(T), 0);
/// <inheritdoc />
public override bool HandleNull => true;
public DefaultUnknownEnumConverter()
{
_enumMemberNamesLazy = new Lazy<Dictionary<T, string>>(
() => EnumMemberValues.ToDictionary(x => x.Value, x => x.Key)
);
}
/// <inheritdoc />
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
if (reader.TokenType != JsonTokenType.String)
if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName))
{
throw new JsonException();
throw new JsonException("Expected String or PropertyName token");
}
var enumText = reader.GetString()?.Replace(" ", "_");
if (Enum.TryParse(typeof(T), enumText, true, out var result))
if (reader.GetString() is { } readerString)
{
return (T)result!;
}
// First try get exact match
if (EnumMemberValues.TryGetValue(readerString, out var enumMemberValue))
{
return enumMemberValue;
}
// Try using enum member values
if (enumText != null)
{
if (EnumMemberValues.TryGetValue(enumText, out var enumMemberResult))
// Otherwise try get case-insensitive match
if (
EnumMemberValues.Keys.FirstOrDefault(
key => key.Equals(readerString, StringComparison.OrdinalIgnoreCase)
) is
{ } enumMemberName
)
{
return enumMemberResult;
return EnumMemberValues[enumMemberName];
}
}
// Unknown value handling
if (Enum.TryParse(typeof(T), "Unknown", true, out var unknownResult))
{
return (T)unknownResult!;
Debug.WriteLine($"Unknown enum member value for {typeToConvert}: {readerString}");
}
throw new JsonException($"Unable to parse '{enumText}' to enum '{typeof(T)}'.");
return UnknownValue;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (value == null)
@ -75,7 +113,7 @@ public class DefaultUnknownEnumConverter<T> : JsonConverter<T>
return;
}
writer.WriteStringValue(value.GetStringValue().Replace("_", " "));
writer.WriteStringValue(EnumMemberNames[value]);
}
/// <inheritdoc />
@ -83,41 +121,12 @@ public class DefaultUnknownEnumConverter<T> : JsonConverter<T>
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
var enumText = reader.GetString()?.Replace(" ", "_");
if (Enum.TryParse(typeof(T), enumText, true, out var result))
{
return (T)result!;
}
// Unknown value handling
if (Enum.TryParse(typeof(T), "Unknown", true, out var unknownResult))
{
return (T)unknownResult!;
}
throw new JsonException($"Unable to parse '{enumText}' to enum '{typeof(T)}'.");
}
) => Read(ref reader, typeToConvert, options);
/// <inheritdoc />
public override void WriteAsPropertyName(
Utf8JsonWriter writer,
T? value,
JsonSerializerOptions options
)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WritePropertyName(value.GetStringValue().Replace("_", " "));
}
) => Write(writer, value, options);
}

30
StabilityMatrix.Core/Helper/ArchiveHelper.cs

@ -86,22 +86,16 @@ public static partial class ArchiveHelper
public static async Task<ArchiveInfo> Extract7Z(string archivePath, string extractDirectory)
{
var args =
$"x {ProcessRunner.Quote(archivePath)} -o{ProcessRunner.Quote(extractDirectory)} -y";
var result = await ProcessRunner
.GetProcessResultAsync(
SevenZipPath,
new[] { "x", archivePath, "-o" + ProcessRunner.Quote(extractDirectory), "-y" }
)
.ConfigureAwait(false);
Logger.Debug($"Starting process '{SevenZipPath}' with arguments '{args}'");
result.EnsureSuccessExitCode();
using var process = new Process();
process.StartInfo = new ProcessStartInfo(SevenZipPath, args)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
process.Start();
await ProcessRunner.WaitForExitConditionAsync(process);
var output = await process.StandardOutput.ReadToEndAsync();
var output = result.StandardOutput ?? "";
try
{
@ -153,8 +147,12 @@ public static partial class ArchiveHelper
$"x {ProcessRunner.Quote(archivePath)} -o{ProcessRunner.Quote(extractDirectory)} -y -bsp1";
Logger.Debug($"Starting process '{SevenZipPath}' with arguments '{args}'");
var process = ProcessRunner.StartProcess(SevenZipPath, args, outputDataReceived: onOutput);
await ProcessRunner.WaitForExitConditionAsync(process);
using var process = ProcessRunner.StartProcess(
SevenZipPath,
args,
outputDataReceived: onOutput
);
await ProcessRunner.WaitForExitConditionAsync(process).ConfigureAwait(false);
progress.Report(new ProgressReport(1f, "Finished extracting", type: ProgressType.Extract));

11
StabilityMatrix.Core/Models/Api/CivitCreator.cs

@ -2,11 +2,14 @@
namespace StabilityMatrix.Core.Models.Api;
public class CivitCreator
public record CivitCreator
{
[JsonPropertyName("username")]
public string Username { get; set; }
public string? Username { get; init; }
[JsonPropertyName("image")]
public string? Image { get; set; }
public string? Image { get; init; }
[JsonIgnore]
public string? ProfileUrl => Username is null ? null : $"https://civitai.com/user/{Username}";
}

9
StabilityMatrix.Core/Models/Api/CivitFileType.cs

@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using StabilityMatrix.Core.Converters.Json;
namespace StabilityMatrix.Core.Models.Api;
@ -6,8 +7,10 @@ namespace StabilityMatrix.Core.Models.Api;
[JsonConverter(typeof(DefaultUnknownEnumConverter<CivitFileType>))]
public enum CivitFileType
{
Unknown,
Model,
VAE,
Training_Data,
Unknown,
[EnumMember(Value = "Training Data")]
TrainingData
}

6
StabilityMatrix.Core/Models/Api/CivitModelFormat.cs

@ -1,12 +1,14 @@
using System.Text.Json.Serialization;
using StabilityMatrix.Core.Converters.Json;
namespace StabilityMatrix.Core.Models.Api;
[JsonConverter(typeof(JsonStringEnumConverter))]
[JsonConverter(typeof(DefaultUnknownEnumConverter<CivitModelFormat>))]
public enum CivitModelFormat
{
Unknown,
SafeTensor,
PickleTensor,
Diffusers,
Other
}

14
StabilityMatrix.Core/Models/Api/CivitModelType.cs

@ -9,29 +9,37 @@ namespace StabilityMatrix.Core.Models.Api;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum CivitModelType
{
Unknown,
[ConvertTo<SharedFolderType>(SharedFolderType.StableDiffusion)]
Checkpoint,
[ConvertTo<SharedFolderType>(SharedFolderType.TextualInversion)]
TextualInversion,
[ConvertTo<SharedFolderType>(SharedFolderType.Hypernetwork)]
Hypernetwork,
[ConvertTo<SharedFolderType>(SharedFolderType.Lora)]
LORA,
[ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)]
Controlnet,
[ConvertTo<SharedFolderType>(SharedFolderType.LyCORIS)]
LoCon,
[ConvertTo<SharedFolderType>(SharedFolderType.VAE)]
VAE,
// Unused/obsolete/unknown/meta options
AestheticGradient,
Model,
MotionModule,
Poses,
Upscaler,
Wildcards,
Workflows,
Other,
All,
Unknown
All
}

2
StabilityMatrix.Core/Models/Packages/A3WebUI.cs

@ -149,7 +149,7 @@ public class A3WebUI : BaseGitPackage
Name = "No Half",
Type = LaunchOptionType.Bool,
Description = "Do not switch the model to 16-bit floats",
InitialValue = HardwareHelper.HasAmdGpu(),
InitialValue = HardwareHelper.PreferRocm() || HardwareHelper.PreferDirectML(),
Options = new() { "--no-half" }
},
new()

2
StabilityMatrix.Core/Models/Packages/FocusControlNet.cs

@ -27,10 +27,8 @@ public class FocusControlNet : Fooocus
public override string Author => "fenneishi";
public override string Blurb =>
"Fooocus-ControlNet adds more control to the original Fooocus software.";
public override string LicenseType => "GPL-3.0";
public override string LicenseUrl =>
"https://github.com/fenneishi/Fooocus-ControlNet-SDXL/blob/main/LICENSE";
public override string LaunchCommand => "launch.py";
public override Uri PreviewImageUri =>
new("https://github.com/fenneishi/Fooocus-ControlNet-SDXL/raw/main/asset/canny/snip.png");
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert;

72
StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs

@ -0,0 +1,72 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
[Singleton(typeof(BasePackage))]
public class RuinedFooocus : Fooocus
{
public RuinedFooocus(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
public override string Name => "RuinedFooocus";
public override string DisplayName { get; set; } = "RuinedFooocus";
public override string Author => "runew0lf";
public override string Blurb =>
"RuinedFooocus combines the best aspects of Stable Diffusion and Midjourney into one seamless, cutting-edge experience";
public override string LicenseUrl =>
"https://github.com/runew0lf/RuinedFooocus/blob/main/LICENSE";
public override Uri PreviewImageUri =>
new("https://raw.githubusercontent.com/runew0lf/pmmconfigs/main/RuinedFooocus_ss.png");
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert;
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
SharedFolderMethod selectedSharedFolderMethod,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
if (torchVersion == TorchVersion.Cuda)
{
var venvRunner = await SetupVenv(installLocation, forceRecreate: true)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true));
await InstallCudaTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);
var requirements = new FilePath(installLocation, "requirements_versions.txt");
await venvRunner
.PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch")
.ConfigureAwait(false);
}
else
{
await base.InstallPackage(
installLocation,
torchVersion,
selectedSharedFolderMethod,
versionOptions,
progress,
onConsoleOutput
)
.ConfigureAwait(false);
}
}
}

1
StabilityMatrix.Core/Models/Settings/TeachingTip.cs

@ -11,6 +11,7 @@ public record TeachingTip(string Value) : StringValue(Value)
{
public static TeachingTip AccountsCredentialsStorageNotice =>
new("AccountsCredentialsStorageNotice");
public static TeachingTip CheckpointCategoriesTip => new("CheckpointCategoriesTip");
/// <inheritdoc />
public override string ToString()

3
StabilityMatrix.Core/Models/SharedFolderType.cs

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using StabilityMatrix.Core.Extensions;
namespace StabilityMatrix.Core.Models;
@ -21,6 +22,8 @@ public enum SharedFolderType
ApproxVAE = 1 << 11,
Karlo = 1 << 12,
DeepDanbooru = 1 << 13,
[Description("TextualInversion (Embeddings)")]
TextualInversion = 1 << 14,
Hypernetwork = 1 << 15,
ControlNet = 1 << 16,

66
StabilityMatrix.Core/Processes/ProcessRunner.cs

@ -401,35 +401,6 @@ public static class ProcessRunner
return inner.Contains(' ') ? $"\"{inner}\"" : argument;
}
/// <summary>
/// Check if the process exited with the expected exit code.
/// </summary>
/// <param name="process">Process to check.</param>
/// <param name="expectedExitCode">Expected exit code.</param>
/// <param name="stdout">Process stdout.</param>
/// <param name="stderr">Process stderr.</param>
/// <exception cref="ProcessException">Thrown if exit code does not match expected value.</exception>
// ReSharper disable once MemberCanBePrivate.Global
public static Task ValidateExitConditionAsync(
Process process,
int expectedExitCode = 0,
string? stdout = null,
string? stderr = null
)
{
var exitCode = process.ExitCode;
if (exitCode == expectedExitCode)
{
return Task.CompletedTask;
}
var pName = process.StartInfo.FileName;
var msg =
$"Process {pName} failed with exit-code {exitCode}. stdout: '{stdout}', stderr: '{stderr}'";
Logger.Error(msg);
throw new ProcessException(msg);
}
/// <summary>
/// Waits for process to exit, then validates exit code.
/// </summary>
@ -443,25 +414,28 @@ public static class ProcessRunner
CancellationToken cancelToken = default
)
{
if (process is AnsiProcess)
if (!process.HasExited)
{
throw new ArgumentException(
$"{nameof(WaitForExitConditionAsync)} does not support AnsiProcess, which uses custom async data reading",
nameof(process)
);
await process.WaitForExitAsync(cancelToken).ConfigureAwait(false);
}
var stdout = new StringBuilder();
var stderr = new StringBuilder();
process.OutputDataReceived += (_, args) => stdout.Append(args.Data);
process.ErrorDataReceived += (_, args) => stderr.Append(args.Data);
await process.WaitForExitAsync(cancelToken).ConfigureAwait(false);
await ValidateExitConditionAsync(
process,
expectedExitCode,
stdout.ToString(),
stderr.ToString()
)
.ConfigureAwait(false);
if (process.ExitCode == expectedExitCode)
{
return;
}
// Accessing ProcessName may error on some platforms
string? processName = null;
try
{
processName = process.ProcessName;
}
catch (SystemException) { }
throw new ProcessException(
"Process "
+ (processName == null ? "" : processName + " ")
+ $"failed with exit-code {process.ExitCode}."
);
}
}

81
StabilityMatrix.Tests/Core/DefaultUnknownEnumConverterTests.cs

@ -0,0 +1,81 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using StabilityMatrix.Core.Converters.Json;
namespace StabilityMatrix.Tests.Core;
[TestClass]
public class DefaultUnknownEnumConverterTests
{
[TestMethod]
[ExpectedException(typeof(JsonException))]
public void TestDeserialize_NormalEnum_ShouldError()
{
const string json = "\"SomeUnknownValue\"";
JsonSerializer.Deserialize<NormalEnum>(json);
}
[TestMethod]
public void TestDeserialize_UnknownEnum_ShouldConvert()
{
const string json = "\"SomeUnknownValue\"";
var result = JsonSerializer.Deserialize<UnknownEnum>(json);
Assert.AreEqual(UnknownEnum.Unknown, result);
}
[TestMethod]
public void TestDeserialize_DefaultEnum_ShouldConvert()
{
const string json = "\"SomeUnknownValue\"";
var result = JsonSerializer.Deserialize<DefaultEnum>(json);
Assert.AreEqual(DefaultEnum.CustomDefault, result);
}
[TestMethod]
public void TestSerialize_UnknownEnum_ShouldConvert()
{
const string expected = "\"Unknown\"";
var result = JsonSerializer.Serialize(UnknownEnum.Unknown);
Assert.AreEqual(expected, result);
}
[TestMethod]
public void TestSerialize_DefaultEnum_ShouldConvert()
{
const string expected = "\"CustomDefault\"";
var result = JsonSerializer.Serialize(DefaultEnum.CustomDefault);
Assert.AreEqual(expected, result);
}
private enum NormalEnum
{
Unknown,
Value1,
Value2
}
[JsonConverter(typeof(DefaultUnknownEnumConverter<UnknownEnum>))]
private enum UnknownEnum
{
Unknown,
Value1,
Value2
}
[JsonConverter(typeof(DefaultUnknownEnumConverter<DefaultEnum>))]
private enum DefaultEnum
{
CustomDefault,
Value1,
Value2
}
}
Loading…
Cancel
Save