Browse Source

Merge branch 'install-refactor' of https://github.com/ionite34/StabilityMatrix into install-refactor

pull/117/head
JT 1 year ago
parent
commit
b1644087c9
  1. 6
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml.cs
  2. 16
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/ChangeColorTypeConverter.cs
  3. 8
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/EventIdConverter.cs
  4. 10
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Extensions/LoggerExtensions.cs
  5. 50
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/DataStoreLoggerConfiguration.cs
  6. 5
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogEntryColor.cs
  7. 4
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogModel.cs
  8. 13
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ObservableObject.cs
  9. 4
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ViewModel.cs
  10. 36
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/DataStoreLoggerTarget.cs
  11. 20
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Extensions/ServicesExtension.cs
  12. 4
      StabilityMatrix.Avalonia.Diagnostics/LogViewer/Logging/LogDataStore.cs
  13. 3
      StabilityMatrix.Avalonia.Diagnostics/ViewModels/LogWindowViewModel.cs
  14. 9
      StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml.cs
  15. 3
      StabilityMatrix.Avalonia/App.axaml
  16. 80
      StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs
  17. 15
      StabilityMatrix.Avalonia/DesignData/MockLiteDbContext.cs
  18. 4
      StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
  19. 237
      StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs
  20. 52
      StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
  21. 124
      StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs
  22. 14
      StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs
  23. 57
      StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs
  24. 47
      StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
  25. 44
      StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
  26. 245
      StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs
  27. 138
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  28. 38
      StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
  29. 226
      StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
  30. 6
      StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs
  31. 6
      StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs
  32. 1
      StabilityMatrix.Avalonia/Views/MainWindow.axaml
  33. 1
      StabilityMatrix.Core/Database/ILiteDbContext.cs
  34. 87
      StabilityMatrix.Core/Database/LiteDbContext.cs
  35. 68
      StabilityMatrix.Core/Helper/SharedFolders.cs
  36. 3
      StabilityMatrix.Core/Models/Database/GitCommit.cs
  37. 3
      StabilityMatrix.Core/Models/Database/LocalModelFile.cs
  38. 29
      StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs
  39. 51
      StabilityMatrix.Core/Models/InstalledPackage.cs
  40. 13
      StabilityMatrix.Core/Models/InstalledPackageVersion.cs
  41. 6
      StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs
  42. 7
      StabilityMatrix.Core/Models/PackageModification/DownloadPackageVersionStep.cs
  43. 6
      StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs
  44. 1
      StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs
  45. 12
      StabilityMatrix.Core/Models/PackageModification/SetupModelFoldersStep.cs
  46. 5
      StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs
  47. 239
      StabilityMatrix.Core/Models/Packages/A3WebUI.cs
  48. 160
      StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
  49. 122
      StabilityMatrix.Core/Models/Packages/BasePackage.cs
  50. 217
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  51. 39
      StabilityMatrix.Core/Models/Packages/DankDiffusion.cs
  52. 151
      StabilityMatrix.Core/Models/Packages/Fooocus.cs
  53. 252
      StabilityMatrix.Core/Models/Packages/InvokeAI.cs
  54. 74
      StabilityMatrix.Core/Models/Packages/UnknownPackage.cs
  55. 379
      StabilityMatrix.Core/Models/Packages/VladAutomatic.cs
  56. 208
      StabilityMatrix.Core/Models/Packages/VoltaML.cs
  57. 7
      StabilityMatrix.Core/Services/ISettingsManager.cs
  58. 37
      StabilityMatrix.Core/Services/ModelIndexService.cs
  59. 163
      StabilityMatrix.Core/Services/SettingsManager.cs
  60. 34
      StabilityMatrix.Core/Services/TrackedDownloadService.cs

6
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml.cs

@ -8,8 +8,7 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls;
public partial class LogViewerControl : UserControl
{
public LogViewerControl()
=> InitializeComponent();
public LogViewerControl() => InitializeComponent();
private ILogDataStoreImpl? vm;
private LogModel? item;
@ -46,7 +45,8 @@ public partial class LogViewerControl : UserControl
{
base.OnDetachedFromLogicalTree(e);
if (vm is null) return;
if (vm is null)
return;
vm.DataStore.Entries.CollectionChanged -= OnCollectionChanged;
}
}

16
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/ChangeColorTypeConverter.cs

@ -13,13 +13,15 @@ public class ChangeColorTypeConverter : IValueConverter
return new SolidColorBrush((Color)(parameter ?? Colors.Black));
var sysDrawColor = (SysDrawColor)value!;
return new SolidColorBrush(Color.FromArgb(
sysDrawColor.A,
sysDrawColor.R,
sysDrawColor.G,
sysDrawColor.B));
return new SolidColorBrush(
Color.FromArgb(sysDrawColor.A, sysDrawColor.R, sysDrawColor.G, sysDrawColor.B)
);
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotImplementedException();
public object ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture
) => throw new NotImplementedException();
}

8
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/EventIdConverter.cs

@ -17,6 +17,10 @@ public class EventIdConverter : IValueConverter
}
// If not implemented, an error is thrown
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> new EventId(0, value?.ToString() ?? string.Empty);
public object ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture
) => new EventId(0, value?.ToString() ?? string.Empty);
}

10
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Extensions/LoggerExtensions.cs

@ -4,8 +4,14 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Extensions;
public static class LoggerExtensions
{
public static void Emit(this ILogger logger, EventId eventId,
LogLevel logLevel, string message, Exception? exception = null, params object?[] args)
public static void Emit(
this ILogger logger,
EventId eventId,
LogLevel logLevel,
string message,
Exception? exception = null,
params object?[] args
)
{
if (logger is null)
return;

50
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/DataStoreLoggerConfiguration.cs

@ -9,39 +9,25 @@ public class DataStoreLoggerConfiguration
public EventId EventId { get; set; }
public Dictionary<LogLevel, LogEntryColor> Colors { get; } = new()
{
[LogLevel.Trace] = new LogEntryColor
public Dictionary<LogLevel, LogEntryColor> Colors { get; } =
new()
{
Foreground = Color.DarkGray
},
[LogLevel.Debug] = new LogEntryColor
{
Foreground = Color.Gray
},
[LogLevel.Information] = new LogEntryColor
{
Foreground = Color.WhiteSmoke,
},
[LogLevel.Warning] = new LogEntryColor
{
Foreground = Color.Orange
},
[LogLevel.Error] = new LogEntryColor
{
Foreground = Color.White,
Background = Color.OrangeRed
},
[LogLevel.Critical] = new LogEntryColor
{
Foreground = Color.White,
Background = Color.Red
},
[LogLevel.None] = new LogEntryColor
{
Foreground = Color.Magenta
}
};
[LogLevel.Trace] = new LogEntryColor { Foreground = Color.DarkGray },
[LogLevel.Debug] = new LogEntryColor { Foreground = Color.Gray },
[LogLevel.Information] = new LogEntryColor { Foreground = Color.WhiteSmoke, },
[LogLevel.Warning] = new LogEntryColor { Foreground = Color.Orange },
[LogLevel.Error] = new LogEntryColor
{
Foreground = Color.White,
Background = Color.OrangeRed
},
[LogLevel.Critical] = new LogEntryColor
{
Foreground = Color.White,
Background = Color.Red
},
[LogLevel.None] = new LogEntryColor { Foreground = Color.Magenta }
};
#endregion
}

5
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogEntryColor.cs

@ -4,9 +4,7 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;
public class LogEntryColor
{
public LogEntryColor()
{
}
public LogEntryColor() { }
public LogEntryColor(Color foreground, Color background)
{
@ -16,5 +14,4 @@ public class LogEntryColor
public Color Foreground { get; set; } = Color.Black;
public Color Background { get; set; } = Color.Transparent;
}

4
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogModel.cs

@ -27,7 +27,5 @@ public class LogModel
#endregion
public string LoggerDisplayName =>
LoggerName?
.Split('.', StringSplitOptions.RemoveEmptyEntries)
.LastOrDefault() ?? "";
LoggerName?.Split('.', StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? "";
}

13
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ObservableObject.cs

@ -5,9 +5,14 @@ namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels;
public class ObservableObject : INotifyPropertyChanged
{
protected bool Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string? propertyName = null)
protected bool Set<TValue>(
ref TValue field,
TValue newValue,
[CallerMemberName] string? propertyName = null
)
{
if (EqualityComparer<TValue>.Default.Equals(field, newValue)) return false;
if (EqualityComparer<TValue>.Default.Equals(field, newValue))
return false;
field = newValue;
OnPropertyChanged(propertyName);
@ -16,6 +21,6 @@ public class ObservableObject : INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

4
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ViewModel.cs

@ -1,3 +1,5 @@
namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels;
public class ViewModel : ObservableObject { /* skip */ }
public class ViewModel
: ObservableObject { /* skip */
}

36
StabilityMatrix.Avalonia.Diagnostics/LogViewer/DataStoreLoggerTarget.cs

@ -54,21 +54,27 @@ public class DataStoreLoggerTarget : TargetWithLayout
}
// add log entry
_dataStore?.AddEntry(new LogModel
{
Timestamp = DateTime.UtcNow,
LogLevel = logLevel,
// do we override the default EventId if it exists?
EventId = eventId.Id == 0 && (_config?.EventId.Id ?? 0) != 0 ? _config!.EventId : eventId,
State = message,
LoggerName = logEvent.LoggerName,
CallerClassName = logEvent.CallerClassName,
CallerMemberName = logEvent.CallerMemberName,
Exception = logEvent.Exception?.Message ?? (logLevel == MsLogLevel.Error ? message : ""),
Color = _config!.Colors[logLevel],
});
Debug.WriteLine($"--- [{logLevel.ToString()[..3]}] {message} - {logEvent.Exception?.Message ?? "no error"}");
_dataStore?.AddEntry(
new LogModel
{
Timestamp = DateTime.UtcNow,
LogLevel = logLevel,
// do we override the default EventId if it exists?
EventId =
eventId.Id == 0 && (_config?.EventId.Id ?? 0) != 0 ? _config!.EventId : eventId,
State = message,
LoggerName = logEvent.LoggerName,
CallerClassName = logEvent.CallerClassName,
CallerMemberName = logEvent.CallerMemberName,
Exception =
logEvent.Exception?.Message ?? (logLevel == MsLogLevel.Error ? message : ""),
Color = _config!.Colors[logLevel],
}
);
Debug.WriteLine(
$"--- [{logLevel.ToString()[..3]}] {message} - {logEvent.Exception?.Message ?? "no error"}"
);
}
#endregion

20
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Extensions/ServicesExtension.cs

@ -23,7 +23,8 @@ public static class ServicesExtension
public static IServiceCollection AddLogViewer(
this IServiceCollection services,
Action<DataStoreLoggerConfiguration> configure)
Action<DataStoreLoggerConfiguration> configure
)
{
services.AddSingleton<ILogDataStore>(Core.Logging.LogDataStore.Instance);
services.AddSingleton<LogViewerControlViewModel>();
@ -32,13 +33,18 @@ public static class ServicesExtension
return services;
}
public static ILoggingBuilder AddNLogTargets(this ILoggingBuilder builder, IConfiguration config)
public static ILoggingBuilder AddNLogTargets(
this ILoggingBuilder builder,
IConfiguration config
)
{
LogManager
.Setup()
// Register custom Target
.SetupExtensions(extensionBuilder =>
extensionBuilder.RegisterTarget<DataStoreLoggerTarget>("DataStoreLogger"));
.SetupExtensions(
extensionBuilder =>
extensionBuilder.RegisterTarget<DataStoreLoggerTarget>("DataStoreLogger")
);
/*builder
.ClearProviders()
@ -56,7 +62,11 @@ public static class ServicesExtension
return builder;
}
public static ILoggingBuilder AddNLogTargets(this ILoggingBuilder builder, IConfiguration config, Action<DataStoreLoggerConfiguration> configure)
public static ILoggingBuilder AddNLogTargets(
this ILoggingBuilder builder,
IConfiguration config,
Action<DataStoreLoggerConfiguration> configure
)
{
builder.AddNLogTargets(config);
builder.Services.Configure(configure);

4
StabilityMatrix.Avalonia.Diagnostics/LogViewer/Logging/LogDataStore.cs

@ -6,8 +6,8 @@ public class LogDataStore : Core.Logging.LogDataStore
{
#region Methods
public override async void AddEntry(Core.Logging.LogModel logModel)
=> await Dispatcher.UIThread.InvokeAsync(() => base.AddEntry(logModel));
public override async void AddEntry(Core.Logging.LogModel logModel) =>
await Dispatcher.UIThread.InvokeAsync(() => base.AddEntry(logModel));
#endregion
}

3
StabilityMatrix.Avalonia.Diagnostics/ViewModels/LogWindowViewModel.cs

@ -14,7 +14,6 @@ public class LogWindowViewModel
public static LogWindowViewModel FromServiceProvider(IServiceProvider services)
{
return new LogWindowViewModel(
services.GetRequiredService<LogViewerControlViewModel>());
return new LogWindowViewModel(services.GetRequiredService<LogViewerControlViewModel>());
}
}

9
StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml.cs

@ -17,12 +17,17 @@ public partial class LogWindow : Window
return Attach(root, serviceProvider, new KeyGesture(Key.F11));
}
public static IDisposable Attach(TopLevel root, IServiceProvider serviceProvider, KeyGesture gesture)
public static IDisposable Attach(
TopLevel root,
IServiceProvider serviceProvider,
KeyGesture gesture
)
{
return (root ?? throw new ArgumentNullException(nameof(root))).AddDisposableHandler(
KeyDownEvent,
PreviewKeyDown,
RoutingStrategies.Tunnel);
RoutingStrategies.Tunnel
);
void PreviewKeyDown(object? sender, KeyEventArgs e)
{

3
StabilityMatrix.Avalonia/App.axaml

@ -22,7 +22,8 @@
</Application.Resources>
<Application.Styles>
<styling:FluentAvaloniaTheme PreferUserAccentColor="True" UseSystemFontOnWindows="True" />
<styling:FluentAvaloniaTheme PreferUserAccentColor="True" UseSystemFontOnWindows="True"
TextVerticalAlignmentOverrideBehavior="Disabled"/>
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml"/>
<StyleInclude Source="avares://AsyncImageLoader.Avalonia/AdvancedImage.axaml" />
<StyleInclude Source="Styles/ProgressRing.axaml"/>

80
StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs

@ -20,17 +20,21 @@ public class BetterContentDialog : ContentDialog
#region Reflection Shenanigans for setting content dialog result
[NotNull]
protected static readonly FieldInfo? ResultField = typeof(ContentDialog).GetField(
"_result",BindingFlags.Instance | BindingFlags.NonPublic);
"_result",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected ContentDialogResult Result
{
get => (ContentDialogResult) ResultField.GetValue(this)!;
get => (ContentDialogResult)ResultField.GetValue(this)!;
set => ResultField.SetValue(this, value);
}
[NotNull]
protected static readonly MethodInfo? HideCoreMethod = typeof(ContentDialog).GetMethod(
"HideCore", BindingFlags.Instance | BindingFlags.NonPublic);
"HideCore",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected void HideCore()
{
@ -40,31 +44,37 @@ public class BetterContentDialog : ContentDialog
// Also get button properties to hide on command execution change
[NotNull]
protected static readonly FieldInfo? PrimaryButtonField = typeof(ContentDialog).GetField(
"_primaryButton", BindingFlags.Instance | BindingFlags.NonPublic);
"_primaryButton",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected Button? PrimaryButton
{
get => (Button?) PrimaryButtonField.GetValue(this)!;
get => (Button?)PrimaryButtonField.GetValue(this)!;
set => PrimaryButtonField.SetValue(this, value);
}
[NotNull]
protected static readonly FieldInfo? SecondaryButtonField = typeof(ContentDialog).GetField(
"_secondaryButton", BindingFlags.Instance | BindingFlags.NonPublic);
"_secondaryButton",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected Button? SecondaryButton
{
get => (Button?) SecondaryButtonField.GetValue(this)!;
get => (Button?)SecondaryButtonField.GetValue(this)!;
set => SecondaryButtonField.SetValue(this, value);
}
[NotNull]
protected static readonly FieldInfo? CloseButtonField = typeof(ContentDialog).GetField(
"_closeButton", BindingFlags.Instance | BindingFlags.NonPublic);
"_closeButton",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected Button? CloseButton
{
get => (Button?) CloseButtonField.GetValue(this)!;
get => (Button?)CloseButtonField.GetValue(this)!;
set => CloseButtonField.SetValue(this, value);
}
@ -87,8 +97,10 @@ public class BetterContentDialog : ContentDialog
protected override Type StyleKeyOverride { get; } = typeof(ContentDialog);
public static readonly StyledProperty<bool> IsFooterVisibleProperty = AvaloniaProperty.Register<BetterContentDialog, bool>(
"IsFooterVisible", true);
public static readonly StyledProperty<bool> IsFooterVisibleProperty = AvaloniaProperty.Register<
BetterContentDialog,
bool
>("IsFooterVisible", true);
public bool IsFooterVisible
{
@ -96,9 +108,11 @@ public class BetterContentDialog : ContentDialog
set => SetValue(IsFooterVisibleProperty, value);
}
public static readonly StyledProperty<ScrollBarVisibility> ContentVerticalScrollBarVisibilityProperty
= AvaloniaProperty.Register<BetterContentDialog, ScrollBarVisibility>(
"ContentScrollBarVisibility", ScrollBarVisibility.Auto);
public static readonly StyledProperty<ScrollBarVisibility> ContentVerticalScrollBarVisibilityProperty =
AvaloniaProperty.Register<BetterContentDialog, ScrollBarVisibility>(
"ContentScrollBarVisibility",
ScrollBarVisibility.Auto
);
public ScrollBarVisibility ContentVerticalScrollBarVisibility
{
@ -106,8 +120,8 @@ public class BetterContentDialog : ContentDialog
set => SetValue(ContentVerticalScrollBarVisibilityProperty, value);
}
public static readonly StyledProperty<double> MinDialogWidthProperty = AvaloniaProperty.Register<BetterContentDialog, double>(
"MinDialogWidth");
public static readonly StyledProperty<double> MinDialogWidthProperty =
AvaloniaProperty.Register<BetterContentDialog, double>("MinDialogWidth");
public double MinDialogWidth
{
@ -115,8 +129,8 @@ public class BetterContentDialog : ContentDialog
set => SetValue(MinDialogWidthProperty, value);
}
public static readonly StyledProperty<double> MaxDialogWidthProperty = AvaloniaProperty.Register<BetterContentDialog, double>(
"MaxDialogWidth");
public static readonly StyledProperty<double> MaxDialogWidthProperty =
AvaloniaProperty.Register<BetterContentDialog, double>("MaxDialogWidth");
public double MaxDialogWidth
{
@ -124,8 +138,8 @@ public class BetterContentDialog : ContentDialog
set => SetValue(MaxDialogWidthProperty, value);
}
public static readonly StyledProperty<double> MaxDialogHeightProperty = AvaloniaProperty.Register<BetterContentDialog, double>(
"MaxDialogHeight");
public static readonly StyledProperty<double> MaxDialogHeightProperty =
AvaloniaProperty.Register<BetterContentDialog, double>("MaxDialogHeight");
public double MaxDialogHeight
{
@ -133,8 +147,8 @@ public class BetterContentDialog : ContentDialog
set => SetValue(MaxDialogHeightProperty, value);
}
public static readonly StyledProperty<Thickness> ContentMarginProperty = AvaloniaProperty.Register<BetterContentDialog, Thickness>(
"ContentMargin");
public static readonly StyledProperty<Thickness> ContentMarginProperty =
AvaloniaProperty.Register<BetterContentDialog, Thickness>("ContentMargin");
public Thickness ContentMargin
{
@ -142,7 +156,6 @@ public class BetterContentDialog : ContentDialog
set => SetValue(ContentMarginProperty, value);
}
public BetterContentDialog()
{
AddHandler(LoadedEvent, OnLoaded);
@ -156,7 +169,10 @@ public class BetterContentDialog : ContentDialog
viewModel.SecondaryButtonClick += OnDialogButtonClick;
viewModel.CloseButtonClick += OnDialogButtonClick;
}
else if ((Content as Control)?.DataContext is ContentDialogProgressViewModelBase progressViewModel)
else if (
(Content as Control)?.DataContext
is ContentDialogProgressViewModelBase progressViewModel
)
{
progressViewModel.PrimaryButtonClick += OnDialogButtonClick;
progressViewModel.SecondaryButtonClick += OnDialogButtonClick;
@ -176,7 +192,8 @@ public class BetterContentDialog : ContentDialog
}
else
{
PrimaryButton.IsVisible = IsPrimaryButtonEnabled && !string.IsNullOrEmpty(PrimaryButtonText);
PrimaryButton.IsVisible =
IsPrimaryButtonEnabled && !string.IsNullOrEmpty(PrimaryButtonText);
}
}
@ -191,7 +208,8 @@ public class BetterContentDialog : ContentDialog
}
else
{
SecondaryButton.IsVisible = IsSecondaryButtonEnabled && !string.IsNullOrEmpty(SecondaryButtonText);
SecondaryButton.IsVisible =
IsSecondaryButtonEnabled && !string.IsNullOrEmpty(SecondaryButtonText);
}
}
@ -260,7 +278,8 @@ public class BetterContentDialog : ContentDialog
var border2 = faBorder?.Child as Border;
// Named Grid 'DialogSpace'
if (border2?.Child is not Grid dialogSpaceGrid) throw new InvalidOperationException("Could not find DialogSpace grid");
if (border2?.Child is not Grid dialogSpaceGrid)
throw new InvalidOperationException("Could not find DialogSpace grid");
var scrollViewer = dialogSpaceGrid.Children[0] as ScrollViewer;
var actualBorder = dialogSpaceGrid.Children[1] as Border;
@ -273,10 +292,11 @@ public class BetterContentDialog : ContentDialog
var subBorder = scrollViewer.Content as Border;
var subGrid = subBorder?.Child as Grid;
if (subGrid is null) throw new InvalidOperationException("Could not find sub grid");
if (subGrid is null)
throw new InvalidOperationException("Could not find sub grid");
var contentControlTitle = subGrid.Children[0] as ContentControl;
// Hide title if empty
if (Title is null or string {Length: 0})
if (Title is null or string { Length: 0 })
{
contentControlTitle!.IsVisible = false;
}
@ -286,7 +306,7 @@ public class BetterContentDialog : ContentDialog
scrollViewer.VerticalScrollBarVisibility = ContentVerticalScrollBarVisibility;
// Also call the vm's OnLoad
if (Content is Control {DataContext: ViewModelBase viewModel})
if (Content is Control { DataContext: ViewModelBase viewModel })
{
viewModel.OnLoaded();
Dispatcher.UIThread.InvokeAsync(viewModel.OnLoadedAsync).SafeFireAndForget();

15
StabilityMatrix.Avalonia/DesignData/MockLiteDbContext.cs

@ -12,11 +12,16 @@ public class MockLiteDbContext : ILiteDbContext
{
public LiteDatabaseAsync Database => throw new NotImplementedException();
public ILiteCollectionAsync<CivitModel> CivitModels => throw new NotImplementedException();
public ILiteCollectionAsync<CivitModelVersion> CivitModelVersions => throw new NotImplementedException();
public ILiteCollectionAsync<CivitModelQueryCacheEntry> CivitModelQueryCache => throw new NotImplementedException();
public ILiteCollectionAsync<LocalModelFile> LocalModelFiles => throw new NotImplementedException();
public Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3)
public ILiteCollectionAsync<CivitModelVersion> CivitModelVersions =>
throw new NotImplementedException();
public ILiteCollectionAsync<CivitModelQueryCacheEntry> CivitModelQueryCache =>
throw new NotImplementedException();
public ILiteCollectionAsync<LocalModelFile> LocalModelFiles =>
throw new NotImplementedException();
public Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(
string hashBlake3
)
{
return Task.FromResult<(CivitModel?, CivitModelVersion?)>((null, null));
}

4
StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs

@ -21,7 +21,5 @@ public class MockModelIndexService : IModelIndexService
}
/// <inheritdoc />
public void BackgroundRefreshIndex()
{
}
public void BackgroundRefreshIndex() { }
}

237
StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs

@ -31,7 +31,6 @@ using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
public partial class InstallerViewModel : ContentDialogViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@ -42,18 +41,41 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
private readonly INotificationService notificationService;
private readonly IPrerequisiteHelper prerequisiteHelper;
[ObservableProperty] private BasePackage selectedPackage;
[ObservableProperty] private PackageVersion? selectedVersion;
[ObservableProperty] private IReadOnlyList<BasePackage>? availablePackages;
[ObservableProperty] private ObservableCollection<GitCommit>? availableCommits;
[ObservableProperty] private ObservableCollection<PackageVersion>? availableVersions;
[ObservableProperty] private GitCommit? selectedCommit;
[ObservableProperty] private string? releaseNotes;
[ObservableProperty] private string latestVersionText = string.Empty;
[ObservableProperty] private bool isAdvancedMode;
[ObservableProperty] private bool showDuplicateWarning;
[ObservableProperty] private string? installName;
[ObservableProperty] private SharedFolderMethod selectedSharedFolderMethod;
[ObservableProperty]
private BasePackage selectedPackage;
[ObservableProperty]
private PackageVersion? selectedVersion;
[ObservableProperty]
private IReadOnlyList<BasePackage>? availablePackages;
[ObservableProperty]
private ObservableCollection<GitCommit>? availableCommits;
[ObservableProperty]
private ObservableCollection<PackageVersion>? availableVersions;
[ObservableProperty]
private GitCommit? selectedCommit;
[ObservableProperty]
private string? releaseNotes;
[ObservableProperty]
private string latestVersionText = string.Empty;
[ObservableProperty]
private bool isAdvancedMode;
[ObservableProperty]
private bool showDuplicateWarning;
[ObservableProperty]
private string? installName;
[ObservableProperty]
private SharedFolderMethod selectedSharedFolderMethod;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowTorchVersionOptions))]
@ -61,8 +83,11 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
// Version types (release or commit)
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ReleaseLabelText),
nameof(IsReleaseMode), nameof(SelectedVersion))]
[NotifyPropertyChangedFor(
nameof(ReleaseLabelText),
nameof(IsReleaseMode),
nameof(SelectedVersion)
)]
private PackageVersionType selectedVersionType = PackageVersionType.Commit;
[ObservableProperty]
@ -70,14 +95,17 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
private PackageVersionType availableVersionTypes =
PackageVersionType.GithubRelease | PackageVersionType.Commit;
public string ReleaseLabelText => IsReleaseMode ? "Version" : "Branch";
public bool IsReleaseMode
{
get => SelectedVersionType == PackageVersionType.GithubRelease;
set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit;
set =>
SelectedVersionType = value
? PackageVersionType.GithubRelease
: PackageVersionType.Commit;
}
public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
public bool IsReleaseModeAvailable =>
AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
public bool ShowTorchVersionOptions => SelectedTorchVersion != TorchVersion.None;
public ProgressViewModel InstallProgress { get; } = new();
@ -87,8 +115,10 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
ISettingsManager settingsManager,
IPackageFactory packageFactory,
IPyRunner pyRunner,
IDownloadService downloadService, INotificationService notificationService,
IPrerequisiteHelper prerequisiteHelper)
IDownloadService downloadService,
INotificationService notificationService,
IPrerequisiteHelper prerequisiteHelper
)
{
this.settingsManager = settingsManager;
this.pyRunner = pyRunner;
@ -97,36 +127,43 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
this.prerequisiteHelper = prerequisiteHelper;
// AvailablePackages and SelectedPackage
AvailablePackages = new ObservableCollection<BasePackage>(packageFactory.GetAllAvailablePackages());
AvailablePackages = new ObservableCollection<BasePackage>(
packageFactory.GetAllAvailablePackages()
);
SelectedPackage = AvailablePackages[0];
}
public override void OnLoaded()
{
if (AvailablePackages == null) return;
if (AvailablePackages == null)
return;
SelectedPackage = AvailablePackages[0];
IsReleaseMode = !SelectedPackage.ShouldIgnoreReleases;
}
public override async Task OnLoadedAsync()
{
if (Design.IsDesignMode) return;
if (Design.IsDesignMode)
return;
// Check for updates
try
{
var versionOptions = await SelectedPackage.GetAllVersionOptions();
if (IsReleaseMode)
{
AvailableVersions =
new ObservableCollection<PackageVersion>(versionOptions.AvailableVersions);
if (!AvailableVersions.Any()) return;
AvailableVersions = new ObservableCollection<PackageVersion>(
versionOptions.AvailableVersions
);
if (!AvailableVersions.Any())
return;
SelectedVersion = AvailableVersions.First(x => !x.IsPrerelease);
}
else
{
AvailableVersions =
new ObservableCollection<PackageVersion>(versionOptions.AvailableBranches);
AvailableVersions = new ObservableCollection<PackageVersion>(
versionOptions.AvailableBranches
);
UpdateSelectedVersionToLatestMain();
}
@ -141,7 +178,10 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
[RelayCommand]
private async Task Install()
{
var result = await notificationService.TryAsync(ActuallyInstall(), "Could not install package");
var result = await notificationService.TryAsync(
ActuallyInstall(),
"Could not install package"
);
if (result.IsSuccessful)
{
OnPrimaryButtonClick();
@ -165,8 +205,13 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
{
if (string.IsNullOrWhiteSpace(InstallName))
{
notificationService.Show(new Notification("Package name is empty",
"Please enter a name for the package", NotificationType.Error));
notificationService.Show(
new Notification(
"Package name is empty",
"Please enter a name for the package",
NotificationType.Error
)
);
return Task.CompletedTask;
}
@ -177,24 +222,36 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
var installedVersion = new InstalledPackageVersion();
if (IsReleaseMode)
{
downloadOptions.VersionTag = SelectedVersion?.TagName ??
throw new NullReferenceException("Selected version is null");
downloadOptions.VersionTag =
SelectedVersion?.TagName
?? throw new NullReferenceException("Selected version is null");
installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag;
}
else
{
downloadOptions.CommitHash = SelectedCommit?.Sha ??
throw new NullReferenceException("Selected commit is null");
installedVersion.InstalledBranch = SelectedVersion?.TagName ??
throw new NullReferenceException("Selected version is null");
downloadOptions.CommitHash =
SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null");
installedVersion.InstalledBranch =
SelectedVersion?.TagName
?? throw new NullReferenceException("Selected version is null");
installedVersion.InstalledCommitSha = downloadOptions.CommitHash;
}
var downloadStep =
new DownloadPackageVersionStep(SelectedPackage, installLocation, downloadOptions);
var installStep = new InstallPackageStep(SelectedPackage, SelectedTorchVersion, installLocation);
var setupModelFoldersStep = new SetupModelFoldersStep(SelectedPackage,
SelectedSharedFolderMethod, installLocation);
var downloadStep = new DownloadPackageVersionStep(
SelectedPackage,
installLocation,
downloadOptions
);
var installStep = new InstallPackageStep(
SelectedPackage,
SelectedTorchVersion,
installLocation
);
var setupModelFoldersStep = new SetupModelFoldersStep(
SelectedPackage,
SelectedSharedFolderMethod,
installLocation
);
var package = new InstalledPackage
{
@ -287,7 +344,8 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
// When changing branch / release modes, refresh
// ReSharper disable once UnusedParameterInPartialMethod
partial void OnSelectedVersionTypeChanged(PackageVersionType value) => OnSelectedPackageChanged(SelectedPackage);
partial void OnSelectedVersionTypeChanged(PackageVersionType value) =>
OnSelectedPackageChanged(SelectedPackage);
partial void OnSelectedPackageChanged(BasePackage value)
{
@ -302,50 +360,55 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
SelectedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod;
SelectedTorchVersion = SelectedPackage.GetRecommendedTorchVersion();
if (Design.IsDesignMode) return;
if (Design.IsDesignMode)
return;
Dispatcher.UIThread.InvokeAsync(async () =>
{
Logger.Debug($"Release mode: {IsReleaseMode}");
var versionOptions = await value.GetAllVersionOptions();
Dispatcher.UIThread
.InvokeAsync(async () =>
{
Logger.Debug($"Release mode: {IsReleaseMode}");
var versionOptions = await value.GetAllVersionOptions();
AvailableVersions = IsReleaseMode
? new ObservableCollection<PackageVersion>(versionOptions.AvailableVersions)
: new ObservableCollection<PackageVersion>(versionOptions.AvailableBranches);
AvailableVersions = IsReleaseMode
? new ObservableCollection<PackageVersion>(versionOptions.AvailableVersions)
: new ObservableCollection<PackageVersion>(versionOptions.AvailableBranches);
SelectedVersion = AvailableVersions.First(x => !x.IsPrerelease);
ReleaseNotes = SelectedVersion.ReleaseNotesMarkdown;
SelectedVersion = AvailableVersions.First(x => !x.IsPrerelease);
ReleaseNotes = SelectedVersion.ReleaseNotesMarkdown;
Logger.Debug($"Loaded release notes for {ReleaseNotes}");
Logger.Debug($"Loaded release notes for {ReleaseNotes}");
if (!IsReleaseMode)
{
var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList();
if (commits is null || commits.Count == 0) return;
if (!IsReleaseMode)
{
var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList();
if (commits is null || commits.Count == 0)
return;
AvailableCommits = new ObservableCollection<GitCommit>(commits);
SelectedCommit = AvailableCommits[0];
UpdateSelectedVersionToLatestMain();
}
AvailableCommits = new ObservableCollection<GitCommit>(commits);
SelectedCommit = AvailableCommits[0];
UpdateSelectedVersionToLatestMain();
}
InstallName = SelectedPackage.DisplayName;
LatestVersionText = IsReleaseMode
? $"Latest version: {SelectedVersion.TagName}"
: $"Branch: {SelectedVersion.TagName}";
}).SafeFireAndForget();
InstallName = SelectedPackage.DisplayName;
LatestVersionText = IsReleaseMode
? $"Latest version: {SelectedVersion.TagName}"
: $"Branch: {SelectedVersion.TagName}";
})
.SafeFireAndForget();
}
partial void OnInstallNameChanged(string? value)
{
ShowDuplicateWarning =
settingsManager.Settings.InstalledPackages.Any(p =>
p.LibraryPath == $"Packages{Path.DirectorySeparatorChar}{value}");
ShowDuplicateWarning = settingsManager.Settings.InstalledPackages.Any(
p => p.LibraryPath == $"Packages{Path.DirectorySeparatorChar}{value}"
);
}
partial void OnSelectedVersionChanged(PackageVersion? value)
{
ReleaseNotes = value?.ReleaseNotesMarkdown ?? string.Empty;
if (value == null) return;
if (value == null)
return;
SelectedCommit = null;
AvailableCommits?.Clear();
@ -353,23 +416,25 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
if (!IsReleaseMode)
{
Task.Run(async () =>
{
try
{
var hashes = await SelectedPackage.GetAllCommits(value.TagName);
if (hashes is null) throw new Exception("No commits found");
Dispatcher.UIThread.Post(() =>
try
{
AvailableCommits = new ObservableCollection<GitCommit>(hashes);
SelectedCommit = AvailableCommits[0];
});
}
catch (Exception e)
{
Logger.Warn($"Error getting commits: {e.Message}");
}
}).SafeFireAndForget();
var hashes = await SelectedPackage.GetAllCommits(value.TagName);
if (hashes is null)
throw new Exception("No commits found");
Dispatcher.UIThread.Post(() =>
{
AvailableCommits = new ObservableCollection<GitCommit>(hashes);
SelectedCommit = AvailableCommits[0];
});
}
catch (Exception e)
{
Logger.Warn($"Error getting commits: {e.Message}");
}
})
.SafeFireAndForget();
}
}
}

52
StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs

@ -27,13 +27,26 @@ public partial class OneClickInstallViewModel : ViewModelBase
private readonly ISharedFolders sharedFolders;
private const string DefaultPackageName = "stable-diffusion-webui";
[ObservableProperty] private string headerText;
[ObservableProperty] private string subHeaderText;
[ObservableProperty] private string subSubHeaderText = string.Empty;
[ObservableProperty] private bool showInstallButton;
[ObservableProperty] private bool isIndeterminate;
[ObservableProperty] private ObservableCollection<BasePackage> allPackages;
[ObservableProperty] private BasePackage selectedPackage;
[ObservableProperty]
private string headerText;
[ObservableProperty]
private string subHeaderText;
[ObservableProperty]
private string subSubHeaderText = string.Empty;
[ObservableProperty]
private bool showInstallButton;
[ObservableProperty]
private bool isIndeterminate;
[ObservableProperty]
private ObservableCollection<BasePackage> allPackages;
[ObservableProperty]
private BasePackage selectedPackage;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsProgressBarVisible))]
@ -41,9 +54,14 @@ public partial class OneClickInstallViewModel : ViewModelBase
public bool IsProgressBarVisible => OneClickInstallProgress > 0 || IsIndeterminate;
public OneClickInstallViewModel(ISettingsManager settingsManager, IPackageFactory packageFactory,
IPrerequisiteHelper prerequisiteHelper, ILogger<OneClickInstallViewModel> logger, IPyRunner pyRunner,
ISharedFolders sharedFolders)
public OneClickInstallViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory,
IPrerequisiteHelper prerequisiteHelper,
ILogger<OneClickInstallViewModel> logger,
IPyRunner pyRunner,
ISharedFolders sharedFolders
)
{
this.settingsManager = settingsManager;
this.packageFactory = packageFactory;
@ -55,9 +73,9 @@ public partial class OneClickInstallViewModel : ViewModelBase
HeaderText = "Welcome to Stability Matrix!";
SubHeaderText = "Choose your preferred interface and click Install to get started!";
ShowInstallButton = true;
AllPackages =
new ObservableCollection<BasePackage>(this.packageFactory.GetAllAvailablePackages()
.Where(p => p.OfferInOneClickInstaller));
AllPackages = new ObservableCollection<BasePackage>(
this.packageFactory.GetAllAvailablePackages().Where(p => p.OfferInOneClickInstaller)
);
SelectedPackage = AllPackages[0];
}
@ -116,8 +134,7 @@ public partial class OneClickInstallViewModel : ViewModelBase
var versionOptions = await SelectedPackage.GetAllVersionOptions();
if (versionOptions.AvailableVersions != null && versionOptions.AvailableVersions.Any())
{
downloadVersion.VersionTag =
versionOptions.AvailableVersions.First().TagName;
downloadVersion.VersionTag = versionOptions.AvailableVersions.First().TagName;
installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag;
}
else
@ -166,7 +183,10 @@ public partial class OneClickInstallViewModel : ViewModelBase
EventManager.Instance.OnOneClickInstallFinished(false);
}
private async Task DownloadPackage(string installLocation, DownloadPackageVersionOptions versionOptions)
private async Task DownloadPackage(
string installLocation,
DownloadPackageVersionOptions versionOptions
)
{
SubHeaderText = "Downloading package...";

124
StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs

@ -30,23 +30,34 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase
private readonly IPackageFactory packageFactory;
private readonly ISettingsManager settingsManager;
[ObservableProperty] private DirectoryPath? packagePath;
[ObservableProperty] private BasePackage? selectedBasePackage;
[ObservableProperty]
private DirectoryPath? packagePath;
public IReadOnlyList<BasePackage> AvailablePackages
=> packageFactory.GetAllAvailablePackages().ToImmutableArray();
[ObservableProperty]
private BasePackage? selectedBasePackage;
[ObservableProperty] private PackageVersion? selectedVersion;
public IReadOnlyList<BasePackage> AvailablePackages =>
packageFactory.GetAllAvailablePackages().ToImmutableArray();
[ObservableProperty] private ObservableCollection<GitCommit>? availableCommits;
[ObservableProperty] private ObservableCollection<PackageVersion>? availableVersions;
[ObservableProperty]
private PackageVersion? selectedVersion;
[ObservableProperty] private GitCommit? selectedCommit;
[ObservableProperty]
private ObservableCollection<GitCommit>? availableCommits;
[ObservableProperty]
private ObservableCollection<PackageVersion>? availableVersions;
[ObservableProperty]
private GitCommit? selectedCommit;
// Version types (release or commit)
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ReleaseLabelText),
nameof(IsReleaseMode), nameof(SelectedVersion))]
[NotifyPropertyChangedFor(
nameof(ReleaseLabelText),
nameof(IsReleaseMode),
nameof(SelectedVersion)
)]
private PackageVersionType selectedVersionType = PackageVersionType.Commit;
[ObservableProperty]
@ -57,14 +68,16 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase
public bool IsReleaseMode
{
get => SelectedVersionType == PackageVersionType.GithubRelease;
set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit;
set =>
SelectedVersionType = value
? PackageVersionType.GithubRelease
: PackageVersionType.Commit;
}
public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
public bool IsReleaseModeAvailable =>
AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
public PackageImportViewModel(
IPackageFactory packageFactory,
ISettingsManager settingsManager)
public PackageImportViewModel(IPackageFactory packageFactory, ISettingsManager settingsManager)
{
this.packageFactory = packageFactory;
this.settingsManager = settingsManager;
@ -74,23 +87,27 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase
{
SelectedBasePackage ??= AvailablePackages[0];
if (Design.IsDesignMode) return;
if (Design.IsDesignMode)
return;
// Populate available versions
try
{
var versionOptions = await SelectedBasePackage.GetAllVersionOptions();
if (IsReleaseMode)
{
AvailableVersions =
new ObservableCollection<PackageVersion>(versionOptions.AvailableVersions);
if (!AvailableVersions.Any()) return;
AvailableVersions = new ObservableCollection<PackageVersion>(
versionOptions.AvailableVersions
);
if (!AvailableVersions.Any())
return;
SelectedVersion = AvailableVersions[0];
}
else
{
AvailableVersions =
new ObservableCollection<PackageVersion>(versionOptions.AvailableBranches);
AvailableVersions = new ObservableCollection<PackageVersion>(
versionOptions.AvailableBranches
);
UpdateSelectedVersionToLatestMain();
}
}
@ -116,8 +133,8 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase
// When changing branch / release modes, refresh
// ReSharper disable once UnusedParameterInPartialMethod
partial void OnSelectedVersionTypeChanged(PackageVersionType value)
=> OnSelectedBasePackageChanged(SelectedBasePackage);
partial void OnSelectedVersionTypeChanged(PackageVersionType value) =>
OnSelectedBasePackageChanged(SelectedBasePackage);
partial void OnSelectedBasePackageChanged(BasePackage? value)
{
@ -133,30 +150,34 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase
AvailableVersionTypes = SelectedBasePackage.AvailableVersionTypes;
if (Design.IsDesignMode) return;
Dispatcher.UIThread.InvokeAsync(async () =>
{
Logger.Debug($"Release mode: {IsReleaseMode}");
var versionOptions = await value.GetAllVersionOptions();
if (Design.IsDesignMode)
return;
AvailableVersions = IsReleaseModeAvailable
? new ObservableCollection<PackageVersion>(versionOptions.AvailableVersions)
: new ObservableCollection<PackageVersion>(versionOptions.AvailableBranches);
Dispatcher.UIThread
.InvokeAsync(async () =>
{
Logger.Debug($"Release mode: {IsReleaseMode}");
var versionOptions = await value.GetAllVersionOptions();
Logger.Debug($"Available versions: {string.Join(", ", AvailableVersions)}");
SelectedVersion = AvailableVersions[0];
AvailableVersions = IsReleaseModeAvailable
? new ObservableCollection<PackageVersion>(versionOptions.AvailableVersions)
: new ObservableCollection<PackageVersion>(versionOptions.AvailableBranches);
if (!IsReleaseMode)
{
var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList();
if (commits is null || commits.Count == 0) return;
Logger.Debug($"Available versions: {string.Join(", ", AvailableVersions)}");
SelectedVersion = AvailableVersions[0];
AvailableCommits = new ObservableCollection<GitCommit>(commits);
SelectedCommit = AvailableCommits[0];
UpdateSelectedVersionToLatestMain();
}
}).SafeFireAndForget();
if (!IsReleaseMode)
{
var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList();
if (commits is null || commits.Count == 0)
return;
AvailableCommits = new ObservableCollection<GitCommit>(commits);
SelectedCommit = AvailableCommits[0];
UpdateSelectedVersionToLatestMain();
}
})
.SafeFireAndForget();
}
private void UpdateSelectedVersionToLatestMain()
@ -187,18 +208,17 @@ public partial class PackageImportViewModel : ContentDialogViewModelBase
var version = new InstalledPackageVersion();
if (IsReleaseMode)
{
version.InstalledReleaseVersion = SelectedVersion?.TagName ??
throw new NullReferenceException(
"Selected version is null");
version.InstalledReleaseVersion =
SelectedVersion?.TagName
?? throw new NullReferenceException("Selected version is null");
}
else
{
version.InstalledBranch = SelectedVersion?.TagName ??
throw new NullReferenceException(
"Selected version is null");
version.InstalledCommitSha = SelectedCommit?.Sha ??
throw new NullReferenceException(
"Selected commit is null");
version.InstalledBranch =
SelectedVersion?.TagName
?? throw new NullReferenceException("Selected version is null");
version.InstalledCommitSha =
SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null");
}
var torchVersion = SelectedBasePackage.GetRecommendedTorchVersion();

14
StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs

@ -16,8 +16,11 @@ public class PackageModificationDialogViewModel : ContentDialogProgressViewModel
private readonly INotificationService notificationService;
private readonly IEnumerable<IPackageStep> steps;
public PackageModificationDialogViewModel(IPackageModificationRunner packageModificationRunner,
INotificationService notificationService, IEnumerable<IPackageStep> steps)
public PackageModificationDialogViewModel(
IPackageModificationRunner packageModificationRunner,
INotificationService notificationService,
IEnumerable<IPackageStep> steps
)
{
this.packageModificationRunner = packageModificationRunner;
this.notificationService = notificationService;
@ -34,8 +37,11 @@ public class PackageModificationDialogViewModel : ContentDialogProgressViewModel
packageModificationRunner.ProgressChanged += PackageModificationRunnerOnProgressChanged;
await packageModificationRunner.ExecuteSteps(steps.ToList());
notificationService.Show("Package Install Completed",
"Package install completed successfully.", NotificationType.Success);
notificationService.Show(
"Package Install Completed",
"Package install completed successfully.",
NotificationType.Success
);
OnCloseButtonClick();
}

57
StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs

@ -31,23 +31,33 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase
private const string InvalidDirectoryText =
"Directory must be empty or have a valid settings.json file";
private const string NotEnoughFreeSpaceText = "Not enough free space on the selected drive";
private const string FatWarningText =
"FAT32 / exFAT drives are not supported at this time";
private const string FatWarningText = "FAT32 / exFAT drives are not supported at this time";
[ObservableProperty] private string dataDirectory = DefaultInstallLocation;
[ObservableProperty] private bool isPortableMode;
[ObservableProperty]
private string dataDirectory = DefaultInstallLocation;
[ObservableProperty] private string directoryStatusText = string.Empty;
[ObservableProperty] private bool isStatusBadgeVisible;
[ObservableProperty] private bool isDirectoryValid;
[ObservableProperty] private bool showFatWarning;
[ObservableProperty]
private bool isPortableMode;
public RefreshBadgeViewModel ValidatorRefreshBadge { get; } = new()
{
State = ProgressState.Inactive,
SuccessToolTipText = ValidExistingDirectoryText,
FailToolTipText = InvalidDirectoryText
};
[ObservableProperty]
private string directoryStatusText = string.Empty;
[ObservableProperty]
private bool isStatusBadgeVisible;
[ObservableProperty]
private bool isDirectoryValid;
[ObservableProperty]
private bool showFatWarning;
public RefreshBadgeViewModel ValidatorRefreshBadge { get; } =
new()
{
State = ProgressState.Inactive,
SuccessToolTipText = ValidExistingDirectoryText,
FailToolTipText = InvalidDirectoryText
};
public bool HasOldData => settingsManager.GetOldInstalledPackages().Any();
@ -90,10 +100,10 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase
try
{
var jsonText = await File.ReadAllTextAsync(settingsPath);
JsonSerializer.Deserialize<Settings>(jsonText, new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
});
JsonSerializer.Deserialize<Settings>(
jsonText,
new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } }
);
// If successful, show existing badge
IsStatusBadgeVisible = true;
IsDirectoryValid = true;
@ -143,13 +153,12 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase
private async Task ShowFolderBrowserDialog()
{
var provider = App.StorageProvider;
var result = await provider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Select Data Folder",
AllowMultiple = false
});
var result = await provider.OpenFolderPickerAsync(
new FolderPickerOpenOptions { Title = "Select Data Folder", AllowMultiple = false }
);
if (result.Count != 1) return;
if (result.Count != 1)
return;
DataDirectory = result[0].Path.LocalPath;
}

47
StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs

@ -24,13 +24,26 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase
public required string Description { get; set; }
public required string Title { get; set; }
[ObservableProperty] private Bitmap? previewImage;
[ObservableProperty] private ModelVersionViewModel? selectedVersionViewModel;
[ObservableProperty] private CivitFileViewModel? selectedFile;
[ObservableProperty] private bool isImportEnabled;
[ObservableProperty] private ObservableCollection<ImageSource> imageUrls = new();
[ObservableProperty] private bool canGoToNextImage;
[ObservableProperty] private bool canGoToPreviousImage;
[ObservableProperty]
private Bitmap? previewImage;
[ObservableProperty]
private ModelVersionViewModel? selectedVersionViewModel;
[ObservableProperty]
private CivitFileViewModel? selectedFile;
[ObservableProperty]
private bool isImportEnabled;
[ObservableProperty]
private ObservableCollection<ImageSource> imageUrls = new();
[ObservableProperty]
private bool canGoToNextImage;
[ObservableProperty]
private bool canGoToPreviousImage;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayedPageNumber))]
@ -38,8 +51,10 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase
public int DisplayedPageNumber => SelectedImageIndex + 1;
public SelectModelVersionViewModel(ISettingsManager settingsManager,
IDownloadService downloadService)
public SelectModelVersionViewModel(
ISettingsManager settingsManager,
IDownloadService downloadService
)
{
this.settingsManager = settingsManager;
this.downloadService = downloadService;
@ -54,12 +69,14 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase
partial void OnSelectedVersionViewModelChanged(ModelVersionViewModel? value)
{
var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled;
var allImages = value?.ModelVersion?.Images?.Where(
img => nsfwEnabled || img.Nsfw == "None")?.Select(x => new ImageSource(x.Url)).ToList();
var allImages = value
?.ModelVersion?.Images?.Where(img => nsfwEnabled || img.Nsfw == "None")
?.Select(x => new ImageSource(x.Url))
.ToList();
if (allImages == null || !allImages.Any())
{
allImages = new List<ImageSource> {new(Assets.NoImage)};
allImages = new List<ImageSource> { new(Assets.NoImage) };
CanGoToNextImage = false;
}
else
@ -93,14 +110,16 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase
public void PreviousImage()
{
if (SelectedImageIndex > 0) SelectedImageIndex--;
if (SelectedImageIndex > 0)
SelectedImageIndex--;
CanGoToPreviousImage = SelectedImageIndex > 0;
CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1;
}
public void NextImage()
{
if (SelectedImageIndex < ImageUrls.Count - 1) SelectedImageIndex++;
if (SelectedImageIndex < ImageUrls.Count - 1)
SelectedImageIndex++;
CanGoToPreviousImage = SelectedImageIndex > 0;
CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1;
}

44
StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs

@ -24,18 +24,29 @@ public partial class UpdateViewModel : ContentDialogViewModelBase
private readonly IHttpClientFactory httpClientFactory;
private readonly IUpdateHelper updateHelper;
[ObservableProperty] private bool isUpdateAvailable;
[ObservableProperty] private UpdateInfo? updateInfo;
[ObservableProperty]
private bool isUpdateAvailable;
[ObservableProperty] private string? releaseNotes;
[ObservableProperty] private string? updateText;
[ObservableProperty] private int progressValue;
[ObservableProperty] private bool showProgressBar;
[ObservableProperty]
private UpdateInfo? updateInfo;
[ObservableProperty]
private string? releaseNotes;
[ObservableProperty]
private string? updateText;
[ObservableProperty]
private int progressValue;
[ObservableProperty]
private bool showProgressBar;
public UpdateViewModel(
ISettingsManager settingsManager,
IHttpClientFactory httpClientFactory,
IUpdateHelper updateHelper)
IUpdateHelper updateHelper
)
{
this.settingsManager = settingsManager;
this.httpClientFactory = httpClientFactory;
@ -51,9 +62,11 @@ public partial class UpdateViewModel : ContentDialogViewModelBase
public override async Task OnLoadedAsync()
{
if (UpdateInfo is null) return;
if (UpdateInfo is null)
return;
UpdateText = $"Stability Matrix v{UpdateInfo.Version} is now available! You currently have v{Compat.AppVersion}. Would you like to update now?";
UpdateText =
$"Stability Matrix v{UpdateInfo.Version} is now available! You currently have v{Compat.AppVersion}. Would you like to update now?";
var client = httpClientFactory.CreateClient();
var response = await client.GetAsync(UpdateInfo.ChangelogUrl);
@ -90,15 +103,18 @@ public partial class UpdateViewModel : ContentDialogViewModelBase
ShowProgressBar = true;
UpdateText = $"Downloading update v{UpdateInfo.Version}...";
await updateHelper.DownloadUpdate(UpdateInfo, new Progress<ProgressReport>(report =>
{
ProgressValue = Convert.ToInt32(report.Percentage);
}));
await updateHelper.DownloadUpdate(
UpdateInfo,
new Progress<ProgressReport>(report =>
{
ProgressValue = Convert.ToInt32(report.Percentage);
})
);
// On unix, we need to set the executable bit
if (Compat.IsUnix)
{
File.SetUnixFileMode(UpdateHelper.ExecutablePath, (UnixFileMode) 0x755);
File.SetUnixFileMode(UpdateHelper.ExecutablePath, (UnixFileMode)0x755);
}
UpdateText = "Update complete. Restarting Stability Matrix in 3 seconds...";

245
StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs

@ -56,22 +56,34 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
private static partial Regex InputYesNoRegex();
public override string Title => "Launch";
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Rocket, IsFilled = true};
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Rocket, IsFilled = true };
public ConsoleViewModel Console { get; } = new();
[ObservableProperty] private bool launchButtonVisibility;
[ObservableProperty] private bool stopButtonVisibility;
[ObservableProperty] private bool isLaunchTeachingTipsOpen;
[ObservableProperty] private bool showWebUiButton;
[ObservableProperty]
private bool launchButtonVisibility;
[ObservableProperty, NotifyPropertyChangedFor(nameof(SelectedBasePackage),
nameof(SelectedPackageExtraCommands))]
[ObservableProperty]
private bool stopButtonVisibility;
[ObservableProperty]
private bool isLaunchTeachingTipsOpen;
[ObservableProperty]
private bool showWebUiButton;
[
ObservableProperty,
NotifyPropertyChangedFor(nameof(SelectedBasePackage), nameof(SelectedPackageExtraCommands))
]
private InstalledPackage? selectedPackage;
[ObservableProperty] private ObservableCollection<InstalledPackage> installedPackages = new();
[ObservableProperty]
private ObservableCollection<InstalledPackage> installedPackages = new();
[ObservableProperty] private BasePackage? runningPackage;
[ObservableProperty]
private BasePackage? runningPackage;
public virtual BasePackage? SelectedBasePackage =>
PackageFactory.FindPackageByName(SelectedPackage?.PackageName);
@ -83,8 +95,11 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
private string webUiUrl = string.Empty;
// Input info-bars
[ObservableProperty] private bool showManualInputPrompt;
[ObservableProperty] private bool showConfirmInputPrompt;
[ObservableProperty]
private bool showManualInputPrompt;
[ObservableProperty]
private bool showConfirmInputPrompt;
public LaunchPageViewModel(
ILogger<LaunchPageViewModel> logger,
@ -93,7 +108,8 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
IPyRunner pyRunner,
INotificationService notificationService,
ISharedFolders sharedFolders,
ServiceManager<ViewModelBase> dialogFactory)
ServiceManager<ViewModelBase> dialogFactory
)
{
this.logger = logger;
this.settingsManager = settingsManager;
@ -103,9 +119,11 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
this.sharedFolders = sharedFolders;
this.dialogFactory = dialogFactory;
settingsManager.RelayPropertyFor(this,
settingsManager.RelayPropertyFor(
this,
vm => vm.SelectedPackage,
settings => settings.ActiveInstalledPackage);
settings => settings.ActiveInstalledPackage
);
EventManager.Instance.PackageLaunchRequested += OnPackageLaunchRequested;
EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;
@ -130,9 +148,11 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
{
if (RunningPackage is not null)
{
notificationService.Show("A package is already running",
notificationService.Show(
"A package is already running",
"Please stop the current package before launching another.",
NotificationType.Error);
NotificationType.Error
);
return;
}
@ -143,14 +163,18 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
public override void OnLoaded()
{
// Ensure active package either exists or is null
settingsManager.Transaction(s =>
{
s.UpdateActiveInstalledPackage();
}, ignoreMissingLibraryDir: true);
settingsManager.Transaction(
s =>
{
s.UpdateActiveInstalledPackage();
},
ignoreMissingLibraryDir: true
);
// Load installed packages
InstalledPackages =
new ObservableCollection<InstalledPackage>(settingsManager.Settings.InstalledPackages);
InstalledPackages = new ObservableCollection<InstalledPackage>(
settingsManager.Settings.InstalledPackages
);
// Load active package
SelectedPackage = settingsManager.Settings.ActiveInstalledPackage;
@ -170,10 +194,13 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
if (activeInstall == null)
{
// No selected package: error notification
notificationService.Show(new Notification(
message: "You must install and select a package before launching",
title: "No package selected",
type: NotificationType.Error));
notificationService.Show(
new Notification(
message: "You must install and select a package before launching",
title: "No package selected",
type: NotificationType.Error
)
);
return;
}
@ -186,11 +213,16 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
{
logger.LogWarning(
"During launch, package name '{PackageName}' did not match a definition",
activeInstallName);
notificationService.Show(new Notification("Package name invalid",
"Install package name did not match a definition. Please reinstall and let us know about this issue.",
NotificationType.Error));
activeInstallName
);
notificationService.Show(
new Notification(
"Package name invalid",
"Install package name did not match a definition. Please reinstall and let us know about this issue.",
NotificationType.Error
)
);
return;
}
@ -205,12 +237,12 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
.FromDefinitions(definitions, Array.Empty<LaunchOption>())
.ToImmutableArray();
var args = cards
.SelectMany(c => c.Options)
.ToList();
var args = cards.SelectMany(c => c.Options).ToList();
logger.LogDebug("Setting initial launch args: {Args}",
string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr())));
logger.LogDebug(
"Setting initial launch args: {Args}",
string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr()))
);
settingsManager.SaveLaunchArgs(activeInstall.Id, args);
}
@ -233,8 +265,10 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
Console.StartUpdates();
// Update shared folder links (in case library paths changed)
await basePackage.UpdateModelFolders(packagePath,
activeInstall.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod);
await basePackage.UpdateModelFolders(
packagePath,
activeInstall.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod
);
// Load user launch args from settings and convert to string
var userArgs = settingsManager.GetLaunchArgs(activeInstall.Id);
@ -249,7 +283,9 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
await basePackage.RunPackage(packagePath, command, userArgsString);
RunningPackage = basePackage;
EventManager.Instance.OnRunningPackageStatusChanged(new PackagePair(activeInstall, basePackage));
EventManager.Instance.OnRunningPackageStatusChanged(
new PackagePair(activeInstall, basePackage)
);
}
// Unpacks sitecustomize.py to the target venv
@ -293,11 +329,11 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
// Open a config page
var userLaunchArgs = settingsManager.GetLaunchArgs(activeInstall.Id);
var viewModel = dialogFactory.Get<LaunchOptionsViewModel>();
viewModel.Cards = LaunchOptionCard.FromDefinitions(definitions, userLaunchArgs)
viewModel.Cards = LaunchOptionCard
.FromDefinitions(definitions, userLaunchArgs)
.ToImmutableArray();
logger.LogDebug("Launching config dialog with cards: {CardsCount}",
viewModel.Cards.Count);
logger.LogDebug("Launching config dialog with cards: {CardsCount}", viewModel.Cards.Count);
var dialog = new BetterContentDialog
{
@ -309,10 +345,7 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
DefaultButton = ContentDialogButton.Primary,
ContentMargin = new Thickness(32, 16),
Padding = new Thickness(0, 16),
Content = new LaunchOptionsDialog
{
DataContext = viewModel,
}
Content = new LaunchOptionsDialog { DataContext = viewModel, }
};
var result = await dialog.ShowAsync();
@ -373,7 +406,8 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
public virtual async Task Stop()
{
if (RunningPackage is null) return;
if (RunningPackage is null)
return;
await RunningPackage.WaitForShutdown();
RunningPackage = null;
@ -384,57 +418,70 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
public void OpenWebUi()
{
if (string.IsNullOrEmpty(webUiUrl)) return;
if (string.IsNullOrEmpty(webUiUrl))
return;
notificationService.TryAsync(Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)),
"Failed to open URL", $"{webUiUrl}");
notificationService.TryAsync(
Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)),
"Failed to open URL",
$"{webUiUrl}"
);
}
private void OnProcessExited(object? sender, int exitCode)
{
EventManager.Instance.OnRunningPackageStatusChanged(null);
Dispatcher.UIThread.InvokeAsync(async () =>
{
logger.LogTrace("Process exited ({Code}) at {Time:g}",
exitCode, DateTimeOffset.Now);
// Need to wait for streams to finish before detaching handlers
if (sender is BaseGitPackage {VenvRunner: not null} package)
Dispatcher.UIThread
.InvokeAsync(async () =>
{
var process = package.VenvRunner.Process;
if (process is not null)
logger.LogTrace(
"Process exited ({Code}) at {Time:g}",
exitCode,
DateTimeOffset.Now
);
// Need to wait for streams to finish before detaching handlers
if (sender is BaseGitPackage { VenvRunner: not null } package)
{
// Max 5 seconds
var ct = new CancellationTokenSource(5000).Token;
try
var process = package.VenvRunner.Process;
if (process is not null)
{
await process.WaitUntilOutputEOF(ct);
}
catch (OperationCanceledException e)
{
logger.LogWarning("Waiting for process EOF timed out: {Message}", e.Message);
// Max 5 seconds
var ct = new CancellationTokenSource(5000).Token;
try
{
await process.WaitUntilOutputEOF(ct);
}
catch (OperationCanceledException e)
{
logger.LogWarning(
"Waiting for process EOF timed out: {Message}",
e.Message
);
}
}
}
}
// Detach handlers
if (sender is BasePackage basePackage)
{
basePackage.ConsoleOutput -= OnProcessOutputReceived;
basePackage.Exited -= OnProcessExited;
basePackage.StartupComplete -= RunningPackageOnStartupComplete;
}
RunningPackage = null;
ShowWebUiButton = false;
await Console.StopUpdatesAsync();
// Need to reset cursor in case its in some weird position
// from progress bars
await Console.ResetWriteCursor();
Console.PostLine($"{Environment.NewLine}Process finished with exit code {exitCode}");
}).SafeFireAndForget();
// Detach handlers
if (sender is BasePackage basePackage)
{
basePackage.ConsoleOutput -= OnProcessOutputReceived;
basePackage.Exited -= OnProcessExited;
basePackage.StartupComplete -= RunningPackageOnStartupComplete;
}
RunningPackage = null;
ShowWebUiButton = false;
await Console.StopUpdatesAsync();
// Need to reset cursor in case its in some weird position
// from progress bars
await Console.ResetWriteCursor();
Console.PostLine(
$"{Environment.NewLine}Process finished with exit code {exitCode}"
);
})
.SafeFireAndForget();
}
// Callback for processes
@ -465,23 +512,29 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
e.Cancel = true;
var dialog = CreateExitConfirmDialog();
Dispatcher.UIThread.InvokeAsync(async () =>
{
if ((TaskDialogStandardResult)
await dialog.ShowAsync(true) == TaskDialogStandardResult.Yes)
Dispatcher.UIThread
.InvokeAsync(async () =>
{
App.Services.GetRequiredService<MainWindow>().Hide();
App.Shutdown();
}
}).SafeFireAndForget();
if (
(TaskDialogStandardResult)await dialog.ShowAsync(true)
== TaskDialogStandardResult.Yes
)
{
App.Services.GetRequiredService<MainWindow>().Hide();
App.Shutdown();
}
})
.SafeFireAndForget();
}
}
}
private static TaskDialog CreateExitConfirmDialog()
{
var dialog = DialogHelper.CreateTaskDialog("Confirm Exit",
"Are you sure you want to exit? This will also close the currently running package.");
var dialog = DialogHelper.CreateTaskDialog(
"Confirm Exit",
"Are you sure you want to exit? This will also close the currently running package."
);
dialog.ShowProgressBar = false;
dialog.FooterVisibility = TaskDialogFooterVisibility.Never;

138
StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs

@ -34,11 +34,20 @@ public partial class PackageCardViewModel : ProgressViewModel
private readonly INavigationService navigationService;
private readonly ServiceManager<ViewModelBase> vmFactory;
[ObservableProperty] private InstalledPackage? package;
[ObservableProperty] private string? cardImageSource;
[ObservableProperty] private bool isUpdateAvailable;
[ObservableProperty] private string? installedVersion;
[ObservableProperty] private bool isUnknownPackage;
[ObservableProperty]
private InstalledPackage? package;
[ObservableProperty]
private string? cardImageSource;
[ObservableProperty]
private bool isUpdateAvailable;
[ObservableProperty]
private string? installedVersion;
[ObservableProperty]
private bool isUnknownPackage;
public PackageCardViewModel(
ILogger<PackageCardViewModel> logger,
@ -46,7 +55,8 @@ public partial class PackageCardViewModel : ProgressViewModel
INotificationService notificationService,
ISettingsManager settingsManager,
INavigationService navigationService,
ServiceManager<ViewModelBase> vmFactory)
ServiceManager<ViewModelBase> vmFactory
)
{
this.logger = logger;
this.packageFactory = packageFactory;
@ -72,8 +82,7 @@ public partial class PackageCardViewModel : ProgressViewModel
IsUnknownPackage = false;
var basePackage = packageFactory[value.PackageName];
CardImageSource = basePackage?.PreviewImageUri.ToString()
?? Assets.NoImage.ToString();
CardImageSource = basePackage?.PreviewImageUri.ToString() ?? Assets.NoImage.ToString();
InstalledVersion = value.Version?.DisplayVersion ?? "Unknown";
}
}
@ -104,7 +113,8 @@ public partial class PackageCardViewModel : ProgressViewModel
var dialog = new ContentDialog
{
Title = "Are you sure?",
Content = "This will delete all folders in the package directory, including any generated images in that directory as well as any files you may have added.",
Content =
"This will delete all folders in the package directory, including any generated images in that directory as well as any files you may have added.",
PrimaryButtonText = "Yes, delete it",
CloseButtonText = "No, keep it",
DefaultButton = ContentDialogButton.Primary
@ -120,13 +130,19 @@ public partial class PackageCardViewModel : ProgressViewModel
var packagePath = new DirectoryPath(settingsManager.LibraryDir, Package.LibraryPath);
var deleteTask = packagePath.DeleteVerboseAsync(logger);
var taskResult = await notificationService.TryAsync(deleteTask,
"Some files could not be deleted. Please close any open files in the package directory and try again.");
var taskResult = await notificationService.TryAsync(
deleteTask,
"Some files could not be deleted. Please close any open files in the package directory and try again."
);
if (taskResult.IsSuccessful)
{
notificationService.Show(new Notification("Success",
$"Package {Package.DisplayName} uninstalled",
NotificationType.Success));
notificationService.Show(
new Notification(
"Success",
$"Package {Package.DisplayName} uninstalled",
NotificationType.Success
)
);
if (!IsUnknownPackage)
{
@ -143,16 +159,21 @@ public partial class PackageCardViewModel : ProgressViewModel
public async Task Update()
{
if (Package is null || IsUnknownPackage) return;
if (Package is null || IsUnknownPackage)
return;
var basePackage = packageFactory[Package.PackageName!];
if (basePackage == null)
{
logger.LogWarning("Could not find package {SelectedPackagePackageName}",
Package.PackageName);
notificationService.Show("Invalid Package type",
logger.LogWarning(
"Could not find package {SelectedPackagePackageName}",
Package.PackageName
);
notificationService.Show(
"Invalid Package type",
$"Package {Package.PackageName.ToRepr()} is not a valid package type",
NotificationType.Error);
NotificationType.Error
);
return;
}
@ -162,9 +183,13 @@ public partial class PackageCardViewModel : ProgressViewModel
IsIndeterminate = true;
var progressId = Guid.NewGuid();
EventManager.Instance.OnProgressChanged(new ProgressItem(progressId,
Package.DisplayName ?? Package.PackageName!,
new ProgressReport(0f, isIndeterminate: true, type: ProgressType.Update)));
EventManager.Instance.OnProgressChanged(
new ProgressItem(
progressId,
Package.DisplayName ?? Package.PackageName!,
new ProgressReport(0f, isIndeterminate: true, type: ProgressType.Update)
)
);
try
{
@ -177,19 +202,22 @@ public partial class PackageCardViewModel : ProgressViewModel
Text = $"Updating {Package.DisplayName}";
EventManager.Instance.OnGlobalProgressChanged(percent);
EventManager.Instance.OnProgressChanged(new ProgressItem(progressId,
packageName, progress));
EventManager.Instance.OnProgressChanged(
new ProgressItem(progressId, packageName, progress)
);
});
var torchVersion = Package.PreferredTorchVersion ??
basePackage.GetRecommendedTorchVersion();
var torchVersion =
Package.PreferredTorchVersion ?? basePackage.GetRecommendedTorchVersion();
var updateResult = await basePackage.Update(Package, torchVersion, progress);
settingsManager.UpdatePackageVersionNumber(Package.Id, updateResult);
notificationService.Show("Update complete",
notificationService.Show(
"Update complete",
$"{Package.DisplayName} has been updated to the latest version.",
NotificationType.Success);
NotificationType.Success
);
await using (settingsManager.BeginTransaction())
{
@ -198,17 +226,30 @@ public partial class PackageCardViewModel : ProgressViewModel
IsUpdateAvailable = false;
InstalledVersion = updateResult.DisplayVersion ?? "Unknown";
EventManager.Instance.OnProgressChanged(new ProgressItem(progressId,
packageName,
new ProgressReport(1f, "Update complete", type: ProgressType.Update)));
EventManager.Instance.OnProgressChanged(
new ProgressItem(
progressId,
packageName,
new ProgressReport(1f, "Update complete", type: ProgressType.Update)
)
);
}
catch (Exception e)
{
logger.LogError(e, "Error Updating Package ({PackageName})", basePackage.Name);
notificationService.ShowPersistent($"Error Updating {Package.DisplayName}", e.Message, NotificationType.Error);
EventManager.Instance.OnProgressChanged(new ProgressItem(progressId,
packageName,
new ProgressReport(0f, "Update failed", type: ProgressType.Update), Failed: true));
notificationService.ShowPersistent(
$"Error Updating {Package.DisplayName}",
e.Message,
NotificationType.Error
);
EventManager.Instance.OnProgressChanged(
new ProgressItem(
progressId,
packageName,
new ProgressReport(0f, "Update failed", type: ProgressType.Update),
Failed: true
)
);
}
finally
{
@ -220,27 +261,23 @@ public partial class PackageCardViewModel : ProgressViewModel
public async Task Import()
{
if (!IsUnknownPackage || Design.IsDesignMode) return;
if (!IsUnknownPackage || Design.IsDesignMode)
return;
var viewModel = vmFactory.Get<PackageImportViewModel>(vm =>
{
vm.PackagePath =
new DirectoryPath(Package?.FullPath ?? throw new InvalidOperationException());
vm.PackagePath = new DirectoryPath(
Package?.FullPath ?? throw new InvalidOperationException()
);
});
var dialog = new TaskDialog
{
Content = new PackageImportDialog
{
DataContext = viewModel
},
Content = new PackageImportDialog { DataContext = viewModel },
ShowProgressBar = false,
Buttons = new List<TaskDialogButton>
{
new(Resources.Action_Import, TaskDialogStandardResult.Yes)
{
IsDefault = true
},
new(Resources.Action_Import, TaskDialogStandardResult.Yes) { IsDefault = true },
new(Resources.Action_Cancel, TaskDialogStandardResult.Cancel)
}
};
@ -257,7 +294,9 @@ public partial class PackageCardViewModel : ProgressViewModel
await using (new MinimumDelay(200, 300))
{
var result = await notificationService.TryAsync(viewModel.AddPackageWithCurrentInputs());
var result = await notificationService.TryAsync(
viewModel.AddPackageWithCurrentInputs()
);
if (result.IsSuccessful)
{
EventManager.Instance.OnInstalledPackagesChanged();
@ -290,8 +329,9 @@ public partial class PackageCardViewModel : ProgressViewModel
if (basePackage == null)
return false;
var canCheckUpdate = Package.LastUpdateCheck == null ||
Package.LastUpdateCheck < DateTime.Now.AddMinutes(-15);
var canCheckUpdate =
Package.LastUpdateCheck == null
|| Package.LastUpdateCheck < DateTime.Now.AddMinutes(-15);
if (!canCheckUpdate)
{

38
StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs

@ -84,11 +84,14 @@ public partial class PackageManagerViewModel : PageViewModelBase
.Or(unknown)
.DeferUntilLoaded()
.Bind(Packages)
.Transform(p => dialogFactory.Get<PackageCardViewModel>(vm =>
{
vm.Package = p;
vm.OnLoadedAsync().SafeFireAndForget();
}))
.Transform(
p =>
dialogFactory.Get<PackageCardViewModel>(vm =>
{
vm.Package = p;
vm.OnLoadedAsync().SafeFireAndForget();
})
)
.Bind(PackageCards)
.Subscribe();
}
@ -108,7 +111,10 @@ public partial class PackageManagerViewModel : PageViewModelBase
if (Design.IsDesignMode)
return;
installedPackages.EditDiff(settingsManager.Settings.InstalledPackages, InstalledPackage.Comparer);
installedPackages.EditDiff(
settingsManager.Settings.InstalledPackages,
InstalledPackage.Comparer
);
var currentUnknown = await Task.Run(IndexUnknownPackages);
unknownInstalledPackages.Edit(s => s.Load(currentUnknown));
@ -135,8 +141,11 @@ public partial class PackageManagerViewModel : PageViewModelBase
if (result == ContentDialogResult.Primary)
{
var steps = viewModel.Steps;
var packageModificationDialogViewModel =
new PackageModificationDialogViewModel(packageModificationRunner, notificationService, steps);
var packageModificationDialogViewModel = new PackageModificationDialogViewModel(
packageModificationRunner,
notificationService,
steps
);
dialog = new BetterContentDialog
{
@ -146,7 +155,10 @@ public partial class PackageManagerViewModel : PageViewModelBase
IsPrimaryButtonEnabled = false,
IsSecondaryButtonEnabled = false,
IsFooterVisible = false,
Content = new PackageModificationDialog {DataContext = packageModificationDialogViewModel}
Content = new PackageModificationDialog
{
DataContext = packageModificationDialogViewModel
}
};
await dialog.ShowAsync();
@ -166,9 +178,11 @@ public partial class PackageManagerViewModel : PageViewModelBase
var currentPackages = settingsManager.Settings.InstalledPackages.ToImmutableArray();
foreach (var subDir in packageDir.Info
.EnumerateDirectories()
.Select(info => new DirectoryPath(info)))
foreach (
var subDir in packageDir.Info
.EnumerateDirectories()
.Select(info => new DirectoryPath(info))
)
{
var expectedLibraryPath = $"Packages{Path.DirectorySeparatorChar}{subDir.Name}";

226
StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs

@ -55,58 +55,59 @@ public partial class SettingsViewModel : PageViewModelBase
public SharedState SharedState { get; }
public override string Title => "Settings";
public override IconSource IconSource => new SymbolIconSource {Symbol = Symbol.Settings, IsFilled = true};
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true };
// ReSharper disable once MemberCanBeMadeStatic.Global
public string AppVersion => $"Version {Compat.AppVersion}" +
(Program.IsDebugBuild ? " (Debug)" : "");
public string AppVersion =>
$"Version {Compat.AppVersion}" + (Program.IsDebugBuild ? " (Debug)" : "");
// Theme section
[ObservableProperty] private string? selectedTheme;
[ObservableProperty]
private string? selectedTheme;
public IReadOnlyList<string> AvailableThemes { get; } = new[]
{
"Light",
"Dark",
"System",
};
public IReadOnlyList<string> AvailableThemes { get; } = new[] { "Light", "Dark", "System", };
[ObservableProperty] private CultureInfo selectedLanguage;
[ObservableProperty]
private CultureInfo selectedLanguage;
// ReSharper disable once MemberCanBeMadeStatic.Global
public IReadOnlyList<CultureInfo> AvailableLanguages => Cultures.SupportedCultures;
public IReadOnlyList<float> AnimationScaleOptions { get; } = new[]
{
0f,
0.25f,
0.5f,
0.75f,
1f,
1.25f,
1.5f,
1.75f,
2f,
};
[ObservableProperty] private float selectedAnimationScale;
public IReadOnlyList<float> AnimationScaleOptions { get; } =
new[] { 0f, 0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f, };
[ObservableProperty]
private float selectedAnimationScale;
// Shared folder options
[ObservableProperty] private bool removeSymlinksOnShutdown;
[ObservableProperty]
private bool removeSymlinksOnShutdown;
// Integrations section
[ObservableProperty] private bool isDiscordRichPresenceEnabled;
[ObservableProperty]
private bool isDiscordRichPresenceEnabled;
// Debug section
[ObservableProperty] private string? debugPaths;
[ObservableProperty] private string? debugCompatInfo;
[ObservableProperty] private string? debugGpuInfo;
[ObservableProperty]
private string? debugPaths;
[ObservableProperty]
private string? debugCompatInfo;
[ObservableProperty]
private string? debugGpuInfo;
// Info section
private const int VersionTapCountThreshold = 7;
[ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))] private int versionTapCount;
[ObservableProperty] private bool isVersionTapTeachingTipOpen;
public string VersionFlyoutText => $"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options.";
[ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))]
private int versionTapCount;
[ObservableProperty]
private bool isVersionTapTeachingTipOpen;
public string VersionFlyoutText =>
$"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options.";
public SettingsViewModel(
INotificationService notificationService,
@ -116,7 +117,8 @@ public partial class SettingsViewModel : PageViewModelBase
ServiceManager<ViewModelBase> dialogFactory,
SharedState sharedState,
ITrackedDownloadService trackedDownloadService,
IModelIndexService modelIndexService)
IModelIndexService modelIndexService
)
{
this.notificationService = notificationService;
this.settingsManager = settingsManager;
@ -133,23 +135,26 @@ public partial class SettingsViewModel : PageViewModelBase
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;
SelectedAnimationScale = settingsManager.Settings.AnimationScale;
settingsManager.RelayPropertyFor(this,
vm => vm.SelectedTheme,
settings => settings.Theme);
settingsManager.RelayPropertyFor(this, vm => vm.SelectedTheme, settings => settings.Theme);
settingsManager.RelayPropertyFor(this,
settingsManager.RelayPropertyFor(
this,
vm => vm.IsDiscordRichPresenceEnabled,
settings => settings.IsDiscordRichPresenceEnabled);
settings => settings.IsDiscordRichPresenceEnabled
);
settingsManager.RelayPropertyFor(this,
settingsManager.RelayPropertyFor(
this,
vm => vm.SelectedAnimationScale,
settings => settings.AnimationScale);
settings => settings.AnimationScale
);
}
partial void OnSelectedThemeChanged(string? value)
{
// In case design / tests
if (Application.Current is null) return;
if (Application.Current is null)
return;
// Change theme
Application.Current.RequestedThemeVariant = value switch
{
@ -161,12 +166,12 @@ public partial class SettingsViewModel : PageViewModelBase
partial void OnSelectedLanguageChanged(CultureInfo? oldValue, CultureInfo newValue)
{
if (oldValue is null || newValue.Name == Cultures.Current.Name) return;
if (oldValue is null || newValue.Name == Cultures.Current.Name)
return;
// Set locale
if (AvailableLanguages.Contains(newValue))
{
Logger.Info("Changing language from {Old} to {New}",
oldValue, newValue);
Logger.Info("Changing language from {Old} to {New}", oldValue, newValue);
Cultures.TrySetSupportedCulture(newValue);
settingsManager.Transaction(s => s.Language = newValue.Name);
@ -191,8 +196,11 @@ public partial class SettingsViewModel : PageViewModelBase
}
else
{
Logger.Info("Requested invalid language change from {Old} to {New}",
oldValue, newValue);
Logger.Info(
"Requested invalid language change from {Old} to {New}",
oldValue,
newValue
);
}
}
@ -205,8 +213,11 @@ public partial class SettingsViewModel : PageViewModelBase
{
settingsManager.Transaction(s => s.InstalledModelHashes = new HashSet<string>());
await Task.Run(() => settingsManager.IndexCheckpoints());
notificationService.Show("Checkpoint cache reset", "The checkpoint cache has been reset.",
NotificationType.Success);
notificationService.Show(
"Checkpoint cache reset",
"The checkpoint cache has been reset.",
NotificationType.Success
);
}
#region Package Environment
@ -217,17 +228,15 @@ public partial class SettingsViewModel : PageViewModelBase
var viewModel = dialogFactory.Get<EnvVarsViewModel>();
// Load current settings
var current = settingsManager.Settings.EnvironmentVariables
?? new Dictionary<string, string>();
var current =
settingsManager.Settings.EnvironmentVariables ?? new Dictionary<string, string>();
viewModel.EnvVars = new ObservableCollection<EnvVarKeyPair>(
current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value)));
current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value))
);
var dialog = new BetterContentDialog
{
Content = new EnvVarsDialog
{
DataContext = viewModel
},
Content = new EnvVarsDialog { DataContext = viewModel },
PrimaryButtonText = "Save",
IsPrimaryButtonEnabled = true,
CloseButtonText = "Cancel",
@ -288,8 +297,7 @@ public partial class SettingsViewModel : PageViewModelBase
{
if (!Compat.IsWindows)
{
notificationService.Show(
"Not supported", "This feature is only supported on Windows.");
notificationService.Show("Not supported", "This feature is only supported on Windows.");
return;
}
@ -297,18 +305,21 @@ public partial class SettingsViewModel : PageViewModelBase
var shortcutDir = new DirectoryPath(
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
"Programs");
"Programs"
);
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk");
var appPath = Compat.AppCurrentPath;
var iconPath = shortcutDir.JoinFile("Stability Matrix.ico");
await Assets.AppIcon.ExtractTo(iconPath);
WindowsShortcuts.CreateShortcut(
shortcutLink, appPath, iconPath, "Stability Matrix");
WindowsShortcuts.CreateShortcut(shortcutLink, appPath, iconPath, "Stability Matrix");
notificationService.Show("Added to Start Menu",
"Stability Matrix has been added to the Start Menu.", NotificationType.Success);
notificationService.Show(
"Added to Start Menu",
"Stability Matrix has been added to the Start Menu.",
NotificationType.Success
);
}
/// <summary>
@ -320,15 +331,15 @@ public partial class SettingsViewModel : PageViewModelBase
{
if (!Compat.IsWindows)
{
notificationService.Show(
"Not supported", "This feature is only supported on Windows.");
notificationService.Show("Not supported", "This feature is only supported on Windows.");
return;
}
// Confirmation dialog
var dialog = new BetterContentDialog
{
Title = "This will create a shortcut for Stability Matrix in the Start Menu for all users",
Title =
"This will create a shortcut for Stability Matrix in the Start Menu for all users",
Content = "You will be prompted for administrator privileges. Continue?",
PrimaryButtonText = "Yes",
CloseButtonText = "Cancel",
@ -344,7 +355,8 @@ public partial class SettingsViewModel : PageViewModelBase
var shortcutDir = new DirectoryPath(
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
"Programs");
"Programs"
);
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk");
var appPath = Compat.AppCurrentPath;
@ -355,19 +367,26 @@ public partial class SettingsViewModel : PageViewModelBase
await Assets.AppIcon.ExtractTo(tempDir.JoinFile("Stability Matrix.ico"));
WindowsShortcuts.CreateShortcut(
tempDir.JoinFile("Stability Matrix.lnk"), appPath, iconPath,
"Stability Matrix");
tempDir.JoinFile("Stability Matrix.lnk"),
appPath,
iconPath,
"Stability Matrix"
);
// Move to target
try
{
var moveLinkResult = await WindowsElevated.MoveFiles(
(tempDir.JoinFile("Stability Matrix.lnk"), shortcutLink),
(tempDir.JoinFile("Stability Matrix.ico"), iconPath));
(tempDir.JoinFile("Stability Matrix.ico"), iconPath)
);
if (moveLinkResult != 0)
{
notificationService.ShowPersistent("Failed to create shortcut", $"Could not copy shortcut",
NotificationType.Error);
notificationService.ShowPersistent(
"Failed to create shortcut",
$"Could not copy shortcut",
NotificationType.Error
);
}
}
catch (Win32Exception e)
@ -378,8 +397,11 @@ public partial class SettingsViewModel : PageViewModelBase
return;
}
notificationService.Show("Added to Start Menu",
"Stability Matrix has been added to the Start Menu for all users.", NotificationType.Success);
notificationService.Show(
"Added to Start Menu",
"Stability Matrix has been added to the Start Menu for all users.",
NotificationType.Success
);
}
public async Task PickNewDataDirectory()
@ -390,10 +412,7 @@ public partial class SettingsViewModel : PageViewModelBase
IsPrimaryButtonEnabled = false,
IsSecondaryButtonEnabled = false,
IsFooterVisible = false,
Content = new SelectDataDirectoryDialog
{
DataContext = viewModel
}
Content = new SelectDataDirectoryDialog { DataContext = viewModel }
};
var result = await dialog.ShowAsync();
@ -466,7 +485,7 @@ public partial class SettingsViewModel : PageViewModelBase
var gpuInfo = "";
foreach (var (i, gpu) in HardwareHelper.IterGpuInfo().Enumerate())
{
gpuInfo += $"[{i+1}] {gpu}\n";
gpuInfo += $"[{i + 1}] {gpu}\n";
}
DebugGpuInfo = gpuInfo;
}
@ -475,10 +494,13 @@ public partial class SettingsViewModel : PageViewModelBase
[RelayCommand]
private void DebugNotification()
{
notificationService.Show(new Notification(
title: "Test Notification",
message: "Here is some message",
type: NotificationType.Information));
notificationService.Show(
new Notification(
title: "Test Notification",
message: "Here is some message",
type: NotificationType.Information
)
);
}
[RelayCommand]
@ -493,8 +515,7 @@ public partial class SettingsViewModel : PageViewModelBase
};
var result = await dialog.ShowAsync();
notificationService.Show(new Notification("Content dialog closed",
$"Result: {result}"));
notificationService.Show(new Notification("Content dialog closed", $"Result: {result}"));
}
[RelayCommand]
@ -515,14 +536,8 @@ public partial class SettingsViewModel : PageViewModelBase
{
var textFields = new TextBoxField[]
{
new()
{
Label = "Url",
},
new()
{
Label = "File path"
}
new() { Label = "Url", },
new() { Label = "File path" }
};
var dialog = DialogHelper.CreateTextEntryDialog("Add download", "", textFields);
@ -542,7 +557,8 @@ public partial class SettingsViewModel : PageViewModelBase
public void OnVersionClick()
{
// Ignore if already enabled
if (SharedState.IsDebugMode) return;
if (SharedState.IsDebugMode)
return;
VersionTapCount++;
@ -555,7 +571,9 @@ public partial class SettingsViewModel : PageViewModelBase
// Enable debug options
SharedState.IsDebugMode = true;
notificationService.Show(
"Debug options enabled", "Warning: Improper use may corrupt application state or cause loss of data.");
"Debug options enabled",
"Warning: Improper use may corrupt application state or cause loss of data."
);
VersionTapCount = 0;
break;
}
@ -579,8 +597,11 @@ public partial class SettingsViewModel : PageViewModelBase
}
catch (Exception e)
{
notificationService.Show("Failed to read licenses information",
$"{e}", NotificationType.Error);
notificationService.Show(
"Failed to read licenses information",
$"{e}",
NotificationType.Error
);
}
}
@ -588,15 +609,17 @@ public partial class SettingsViewModel : PageViewModelBase
{
// Read licenses.json
using var reader = new StreamReader(Assets.LicensesJson.Open());
var licenses = JsonSerializer
.Deserialize<IReadOnlyList<LicenseInfo>>(reader.ReadToEnd()) ??
throw new InvalidOperationException("Failed to read licenses.json");
var licenses =
JsonSerializer.Deserialize<IReadOnlyList<LicenseInfo>>(reader.ReadToEnd())
?? throw new InvalidOperationException("Failed to read licenses.json");
// Generate markdown
var builder = new StringBuilder();
foreach (var license in licenses)
{
builder.AppendLine($"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(", ", license.Authors)}");
builder.AppendLine(
$"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(", ", license.Authors)}"
);
builder.AppendLine();
builder.AppendLine(license.Description);
builder.AppendLine();
@ -608,5 +631,4 @@ public partial class SettingsViewModel : PageViewModelBase
}
#endregion
}

6
StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs

@ -29,7 +29,8 @@ public partial class PackageModificationDialog : UserControlBase
var textMate = editor.InstallTextMate(options);
var scope = options.GetScopeByLanguageId("log");
if (scope is null) throw new InvalidOperationException("Scope is null");
if (scope is null)
throw new InvalidOperationException("Scope is null");
textMate.SetGrammar(scope);
textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus));
@ -40,7 +41,8 @@ public partial class PackageModificationDialog : UserControlBase
Dispatcher.UIThread.Invoke(() =>
{
var editor = this.FindControl<TextEditor>("Console");
if (editor?.Document == null) return;
if (editor?.Document == null)
return;
var line = Math.Max(editor.Document.LineCount - 5, 1);
editor.ScrollToLine(line);
});

6
StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs

@ -31,7 +31,8 @@ public partial class LaunchPageView : UserControlBase
var textMate = editor.InstallTextMate(options);
var scope = options.GetScopeByLanguageId("log");
if (scope is null) throw new InvalidOperationException("Scope is null");
if (scope is null)
throw new InvalidOperationException("Scope is null");
textMate.SetGrammar(scope);
textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus));
@ -55,7 +56,8 @@ public partial class LaunchPageView : UserControlBase
Dispatcher.UIThread.Invoke(() =>
{
var editor = this.FindControl<TextEditor>("Console");
if (editor?.Document == null) return;
if (editor?.Document == null)
return;
var line = Math.Max(editor.Document.LineCount - LineOffset, 1);
editor.ScrollToLine(line);
});

1
StabilityMatrix.Avalonia/Views/MainWindow.axaml

@ -16,6 +16,7 @@
Width="1100"
Height="750"
Title="Stability Matrix"
FontFamily="San Francisco, Segoe UI, Helvetica Neue, Helvetica, Arial, sans-serif"
x:Class="StabilityMatrix.Avalonia.Views.MainWindow">
<Grid RowDefinitions="Auto,Auto,*">

1
StabilityMatrix.Core/Database/ILiteDbContext.cs

@ -13,7 +13,6 @@ public interface ILiteDbContext : IDisposable
ILiteCollectionAsync<CivitModelQueryCacheEntry> CivitModelQueryCache { get; }
ILiteCollectionAsync<LocalModelFile> LocalModelFiles { get; }
Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3);
Task<bool> UpsertCivitModelAsync(CivitModel civitModel);
Task<bool> UpsertCivitModelAsync(IEnumerable<CivitModel> civitModels);

87
StabilityMatrix.Core/Database/LiteDbContext.cs

@ -23,16 +23,22 @@ public class LiteDbContext : ILiteDbContext
public event EventHandler? CivitModelsChanged;
// Collections (Tables)
public ILiteCollectionAsync<CivitModel> CivitModels => Database.GetCollection<CivitModel>("CivitModels");
public ILiteCollectionAsync<CivitModelVersion> CivitModelVersions => Database.GetCollection<CivitModelVersion>("CivitModelVersions");
public ILiteCollectionAsync<CivitModelQueryCacheEntry> CivitModelQueryCache => Database.GetCollection<CivitModelQueryCacheEntry>("CivitModelQueryCache");
public ILiteCollectionAsync<GithubCacheEntry> GithubCache => Database.GetCollection<GithubCacheEntry>("GithubCache");
public ILiteCollectionAsync<LocalModelFile> LocalModelFiles => Database.GetCollection<LocalModelFile>("LocalModelFiles");
public ILiteCollectionAsync<CivitModel> CivitModels =>
Database.GetCollection<CivitModel>("CivitModels");
public ILiteCollectionAsync<CivitModelVersion> CivitModelVersions =>
Database.GetCollection<CivitModelVersion>("CivitModelVersions");
public ILiteCollectionAsync<CivitModelQueryCacheEntry> CivitModelQueryCache =>
Database.GetCollection<CivitModelQueryCacheEntry>("CivitModelQueryCache");
public ILiteCollectionAsync<GithubCacheEntry> GithubCache =>
Database.GetCollection<GithubCacheEntry>("GithubCache");
public ILiteCollectionAsync<LocalModelFile> LocalModelFiles =>
Database.GetCollection<LocalModelFile>("LocalModelFiles");
public LiteDbContext(
ILogger<LiteDbContext> logger,
ISettingsManager settingsManager,
IOptions<DebugOptions> debugOptions)
IOptions<DebugOptions> debugOptions
)
{
this.logger = logger;
this.settingsManager = settingsManager;
@ -53,15 +59,20 @@ public class LiteDbContext : ILiteDbContext
try
{
var dbPath = Path.Combine(settingsManager.LibraryDir, "StabilityMatrix.db");
db = new LiteDatabaseAsync(new ConnectionString()
{
Filename = dbPath,
Connection = ConnectionType.Shared,
});
db = new LiteDatabaseAsync(
new ConnectionString()
{
Filename = dbPath,
Connection = ConnectionType.Shared,
}
);
}
catch (IOException e)
{
logger.LogWarning("Database in use or not accessible ({Message}), using temporary database", e.Message);
logger.LogWarning(
"Database in use or not accessible ({Message}), using temporary database",
e.Message
);
}
}
@ -69,30 +80,43 @@ public class LiteDbContext : ILiteDbContext
db ??= new LiteDatabaseAsync(":temp:");
// Register reference fields
LiteDBExtensions.Register<CivitModel, CivitModelVersion>(m => m.ModelVersions, "CivitModelVersions");
LiteDBExtensions.Register<CivitModelQueryCacheEntry, CivitModel>(e => e.Items, "CivitModels");
LiteDBExtensions.Register<CivitModel, CivitModelVersion>(
m => m.ModelVersions,
"CivitModelVersions"
);
LiteDBExtensions.Register<CivitModelQueryCacheEntry, CivitModel>(
e => e.Items,
"CivitModels"
);
return db;
}
public async Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3)
public async Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(
string hashBlake3
)
{
var version = await CivitModelVersions.Query()
.Where(mv => mv.Files!
.Select(f => f.Hashes)
.Select(hashes => hashes.BLAKE3)
.Any(hash => hash == hashBlake3))
var version = await CivitModelVersions
.Query()
.Where(
mv =>
mv.Files!
.Select(f => f.Hashes)
.Select(hashes => hashes.BLAKE3)
.Any(hash => hash == hashBlake3)
)
.FirstOrDefaultAsync()
.ConfigureAwait(false);
if (version is null) return (null, null);
if (version is null)
return (null, null);
var model = await CivitModels.Query()
var model = await CivitModels
.Query()
.Include(m => m.ModelVersions)
.Where(m => m.ModelVersions!
.Select(v => v.Id)
.Any(id => id == version.Id))
.FirstOrDefaultAsync().ConfigureAwait(false);
.Where(m => m.ModelVersions!.Select(v => v.Id).Any(id => id == version.Id))
.FirstOrDefaultAsync()
.ConfigureAwait(false);
return (model, version);
}
@ -100,7 +124,9 @@ public class LiteDbContext : ILiteDbContext
public async Task<bool> UpsertCivitModelAsync(CivitModel civitModel)
{
// Insert model versions first then model
var versionsUpdated = await CivitModelVersions.UpsertAsync(civitModel.ModelVersions).ConfigureAwait(false);
var versionsUpdated = await CivitModelVersions
.UpsertAsync(civitModel.ModelVersions)
.ConfigureAwait(false);
var updated = await CivitModels.UpsertAsync(civitModel).ConfigureAwait(false);
// Notify listeners on any change
var anyUpdated = versionsUpdated > 0 || updated;
@ -141,7 +167,8 @@ public class LiteDbContext : ILiteDbContext
public async Task<GithubCacheEntry?> GetGithubCacheEntry(string? cacheKey)
{
if (string.IsNullOrEmpty(cacheKey)) return null;
if (string.IsNullOrEmpty(cacheKey))
return null;
if (await GithubCache.FindByIdAsync(cacheKey).ConfigureAwait(false) is { } result)
{
@ -162,9 +189,7 @@ public class LiteDbContext : ILiteDbContext
{
database.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (ObjectDisposedException) { }
database = null;
}

68
StabilityMatrix.Core/Helper/SharedFolders.cs

@ -36,8 +36,11 @@ public class SharedFolders : ISharedFolders
}
}
public static void SetupLinks(Dictionary<SharedFolderType, IReadOnlyList<string>> definitions,
DirectoryPath modelsDirectory, DirectoryPath installDirectory)
public static void SetupLinks(
Dictionary<SharedFolderType, IReadOnlyList<string>> definitions,
DirectoryPath modelsDirectory,
DirectoryPath installDirectory
)
{
foreach (var (folderType, relativePaths) in definitions)
{
@ -63,7 +66,9 @@ public class SharedFolders : ISharedFolders
// Skip name collisions
if (File.Exists(sourceFile))
{
Logger.Warn($"Skipping file {file.FullName} because it already exists in {sourceDir}");
Logger.Warn(
$"Skipping file {file.FullName} because it already exists in {sourceDir}"
);
continue;
}
destinationFile.Info.MoveTo(sourceFile);
@ -81,7 +86,8 @@ public class SharedFolders : ISharedFolders
{
var modelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);
var sharedFolders = basePackage.SharedFolders;
if (sharedFolders == null) return;
if (sharedFolders == null)
return;
SetupLinks(sharedFolders, modelsDirectory, installDirectory);
}
@ -89,10 +95,15 @@ public class SharedFolders : ISharedFolders
/// Deletes junction links and remakes them. Unlike SetupLinksForPackage,
/// this will not copy files from the destination to the source.
/// </summary>
public static async Task UpdateLinksForPackage(BasePackage basePackage, DirectoryPath modelsDirectory, DirectoryPath installDirectory)
public static async Task UpdateLinksForPackage(
BasePackage basePackage,
DirectoryPath modelsDirectory,
DirectoryPath installDirectory
)
{
var sharedFolders = basePackage.SharedFolders;
if (sharedFolders is null) return;
if (sharedFolders is null)
return;
foreach (var (folderType, relativePaths) in sharedFolders)
{
@ -117,7 +128,8 @@ public class SharedFolders : ISharedFolders
if (destinationDir.Info.LinkTarget == sourceDir)
{
Logger.Info(
$"Skipped updating matching folder link ({destinationDir} -> ({sourceDir})");
$"Skipped updating matching folder link ({destinationDir} -> ({sourceDir})"
);
return;
}
@ -131,8 +143,12 @@ public class SharedFolders : ISharedFolders
if (destinationDir.Info.EnumerateFileSystemInfos().Any())
{
Logger.Info($"Moving files from {destinationDir} to {sourceDir}");
await FileTransfers.MoveAllFilesAndDirectories(
destinationDir, sourceDir, overwriteIfHashMatches: true)
await FileTransfers
.MoveAllFilesAndDirectories(
destinationDir,
sourceDir,
overwriteIfHashMatches: true
)
.ConfigureAwait(false);
}
@ -161,7 +177,8 @@ public class SharedFolders : ISharedFolders
{
var destination = Path.GetFullPath(Path.Combine(installPath, relativePath));
// Delete the destination folder if it exists
if (!Directory.Exists(destination)) continue;
if (!Directory.Exists(destination))
continue;
Logger.Info($"Deleting junction target {destination}");
Directory.Delete(destination, false);
@ -174,22 +191,32 @@ public class SharedFolders : ISharedFolders
var packages = settingsManager.Settings.InstalledPackages;
foreach (var package in packages)
{
if (package.PackageName == null) continue;
if (package.PackageName == null)
continue;
var basePackage = packageFactory[package.PackageName];
if (basePackage == null) continue;
if (package.LibraryPath == null) continue;
if (basePackage == null)
continue;
if (package.LibraryPath == null)
continue;
try
{
var sharedFolderMethod = package.PreferredSharedFolderMethod ??
basePackage.RecommendedSharedFolderMethod;
basePackage.RemoveModelFolderLinks(package.FullPath, sharedFolderMethod)
.GetAwaiter().GetResult();
var sharedFolderMethod =
package.PreferredSharedFolderMethod
?? basePackage.RecommendedSharedFolderMethod;
basePackage
.RemoveModelFolderLinks(package.FullPath, sharedFolderMethod)
.GetAwaiter()
.GetResult();
}
catch (Exception e)
{
Logger.Warn("Failed to remove links for package {Package} " +
"({DisplayName}): {Message}", package.PackageName, package.DisplayName, e.Message);
Logger.Warn(
"Failed to remove links for package {Package} " + "({DisplayName}): {Message}",
package.PackageName,
package.DisplayName,
e.Message
);
}
}
}
@ -197,7 +224,8 @@ public class SharedFolders : ISharedFolders
public void SetupSharedModelFolders()
{
var modelsDir = settingsManager.ModelsDirectory;
if (string.IsNullOrWhiteSpace(modelsDir)) return;
if (string.IsNullOrWhiteSpace(modelsDir))
return;
Directory.CreateDirectory(modelsDir);
var allSharedFolderTypes = Enum.GetValues<SharedFolderType>();

3
StabilityMatrix.Core/Models/Database/GitCommit.cs

@ -6,5 +6,6 @@ public class GitCommit
{
public string? Sha { get; set; }
[JsonIgnore] public string ShortSha => string.IsNullOrWhiteSpace(Sha) ? string.Empty : Sha[..7];
[JsonIgnore]
public string ShortSha => string.IsNullOrWhiteSpace(Sha) ? string.Empty : Sha[..7];
}

3
StabilityMatrix.Core/Models/Database/LocalModelFile.cs

@ -35,7 +35,8 @@ public class LocalModelFile
public string? GetPreviewImageFullPath(string rootModelDirectory)
{
return PreviewImageRelativePath == null ? null
return PreviewImageRelativePath == null
? null
: Path.Combine(rootModelDirectory, PreviewImageRelativePath);
}

29
StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs

@ -10,6 +10,7 @@ namespace StabilityMatrix.Core.Models.FileInterfaces;
public class FilePath : FileSystemPath, IPathObject
{
private FileInfo? _info;
// ReSharper disable once MemberCanBePrivate.Global
[JsonIgnore]
public FileInfo Info => _info ??= new FileInfo(FullPath);
@ -31,8 +32,7 @@ public class FilePath : FileSystemPath, IPathObject
public string Name => Info.Name;
[JsonIgnore]
public string NameWithoutExtension
=> Path.GetFileNameWithoutExtension(Info.Name);
public string NameWithoutExtension => Path.GetFileNameWithoutExtension(Info.Name);
/// <summary>
/// Get the directory of the file.
@ -44,8 +44,7 @@ public class FilePath : FileSystemPath, IPathObject
{
try
{
return Info.Directory == null ? null
: new DirectoryPath(Info.Directory);
return Info.Directory == null ? null : new DirectoryPath(Info.Directory);
}
catch (DirectoryNotFoundException)
{
@ -54,22 +53,20 @@ public class FilePath : FileSystemPath, IPathObject
}
}
public FilePath(string path) : base(path)
{
}
public FilePath(string path)
: base(path) { }
public FilePath(FileInfo fileInfo) : base(fileInfo.FullName)
public FilePath(FileInfo fileInfo)
: base(fileInfo.FullName)
{
_info = fileInfo;
}
public FilePath(FileSystemPath path) : base(path)
{
}
public FilePath(FileSystemPath path)
: base(path) { }
public FilePath(params string[] paths) : base(paths)
{
}
public FilePath(params string[] paths)
: base(paths) { }
public long GetSize()
{
@ -79,7 +76,8 @@ public class FilePath : FileSystemPath, IPathObject
public long GetSize(bool includeSymbolicLinks)
{
if (!includeSymbolicLinks && IsSymbolicLink) return 0;
if (!includeSymbolicLinks && IsSymbolicLink)
return 0;
return GetSize();
}
@ -174,5 +172,6 @@ public class FilePath : FileSystemPath, IPathObject
// Implicit conversions to and from string
public static implicit operator string(FilePath path) => path.FullPath;
public static implicit operator FilePath(string path) => new(path);
}

51
StabilityMatrix.Core/Models/InstalledPackage.cs

@ -10,8 +10,10 @@ public class InstalledPackage : IJsonOnDeserialized
{
// Unique ID for the installation
public Guid Id { get; set; }
// User defined name
public string? DisplayName { get; set; }
// Package name
public string? PackageName { get; set; }
@ -40,7 +42,8 @@ public class InstalledPackage : IJsonOnDeserialized
/// Full path to the package, using LibraryPath and GlobalConfig.LibraryDir.
/// </summary>
[JsonIgnore]
public string? FullPath => LibraryPath != null ? System.IO.Path.Combine(GlobalConfig.LibraryDir, LibraryPath) : null;
public string? FullPath =>
LibraryPath != null ? System.IO.Path.Combine(GlobalConfig.LibraryDir, LibraryPath) : null;
public string? LaunchCommand { get; set; }
public List<LaunchOption>? LaunchArgs { get; set; }
@ -57,12 +60,14 @@ public class InstalledPackage : IJsonOnDeserialized
{
var relativePath = System.IO.Path.GetRelativePath(relativeTo, path);
// GetRelativePath returns the path if it's not relative
if (relativePath == path) return null;
if (relativePath == path)
return null;
// Further check if the path is a sub-path of the library
var isSubPath = relativePath != "."
&& relativePath != ".."
&& !relativePath.StartsWith(".." + System.IO.Path.DirectorySeparatorChar)
&& !System.IO.Path.IsPathRooted(relativePath);
var isSubPath =
relativePath != "."
&& relativePath != ".."
&& !relativePath.StartsWith(".." + System.IO.Path.DirectorySeparatorChar)
&& !System.IO.Path.IsPathRooted(relativePath);
return isSubPath ? relativePath : null;
}
@ -76,7 +81,8 @@ public class InstalledPackage : IJsonOnDeserialized
#pragma warning disable CS0618
var oldPath = Path;
#pragma warning restore CS0618
if (oldPath == null) return false;
if (oldPath == null)
return false;
// Check if the path is a sub-path of the library
var library = libraryDirectory ?? GlobalConfig.LibraryDir;
@ -105,7 +111,8 @@ public class InstalledPackage : IJsonOnDeserialized
#pragma warning disable CS0618
var oldPath = Path;
#pragma warning restore CS0618
if (oldPath == null) return false;
if (oldPath == null)
return false;
// Check if the path is a sub-path of the library
var library = libraryDirectory ?? GlobalConfig.LibraryDir;
@ -123,7 +130,8 @@ public class InstalledPackage : IJsonOnDeserialized
#pragma warning disable CS0618
var oldPath = Path;
#pragma warning restore CS0618
if (oldPath == null) return;
if (oldPath == null)
return;
var libDir = libraryDirectory ?? GlobalConfig.LibraryDir;
// if old package Path is same as new library, return
@ -138,7 +146,8 @@ public class InstalledPackage : IJsonOnDeserialized
}
// Try using pure migration first
if (TryPureMigratePath(libraryDirectory)) return;
if (TryPureMigratePath(libraryDirectory))
return;
// If not, we need to move the package directory
var packageFolderName = new DirectoryInfo(oldPath).Name;
@ -153,7 +162,10 @@ public class InstalledPackage : IJsonOnDeserialized
var suffix = 2;
while (Directory.Exists(newPackagePath))
{
newPackagePath = System.IO.Path.Combine(newPackagesDir, $"{packageFolderName}-{suffix}");
newPackagePath = System.IO.Path.Combine(
newPackagesDir,
$"{packageFolderName}-{suffix}"
);
suffix++;
}
@ -177,9 +189,11 @@ public class InstalledPackage : IJsonOnDeserialized
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj.GetType() == this.GetType() && Equals((InstalledPackage) obj);
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
return obj.GetType() == this.GetType() && Equals((InstalledPackage)obj);
}
public override int GetHashCode()
@ -194,13 +208,12 @@ public class InstalledPackage : IJsonOnDeserialized
if (Version != null)
return;
if (string.IsNullOrWhiteSpace(InstalledBranch) && !string.IsNullOrWhiteSpace(PackageVersion))
if (
string.IsNullOrWhiteSpace(InstalledBranch) && !string.IsNullOrWhiteSpace(PackageVersion)
)
{
// release mode
Version = new InstalledPackageVersion
{
InstalledReleaseVersion = PackageVersion
};
Version = new InstalledPackageVersion { InstalledReleaseVersion = PackageVersion };
}
else if (!string.IsNullOrWhiteSpace(PackageVersion))
{

13
StabilityMatrix.Core/Models/InstalledPackageVersion.cs

@ -12,9 +12,12 @@ public class InstalledPackageVersion
public bool IsReleaseMode => string.IsNullOrWhiteSpace(InstalledBranch);
[JsonIgnore]
public string DisplayVersion => (IsReleaseMode
? InstalledReleaseVersion
: string.IsNullOrWhiteSpace(InstalledCommitSha)
? InstalledBranch
: $"{InstalledBranch}@{InstalledCommitSha[..7]}") ?? string.Empty;
public string DisplayVersion =>
(
IsReleaseMode
? InstalledReleaseVersion
: string.IsNullOrWhiteSpace(InstalledCommitSha)
? InstalledBranch
: $"{InstalledBranch}@{InstalledCommitSha[..7]}"
) ?? string.Empty;
}

6
StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs

@ -8,8 +8,10 @@ public class AddInstalledPackageStep : IPackageStep
private readonly ISettingsManager settingsManager;
private readonly InstalledPackage newInstalledPackage;
public AddInstalledPackageStep(ISettingsManager settingsManager,
InstalledPackage newInstalledPackage)
public AddInstalledPackageStep(
ISettingsManager settingsManager,
InstalledPackage newInstalledPackage
)
{
this.settingsManager = settingsManager;
this.newInstalledPackage = newInstalledPackage;

7
StabilityMatrix.Core/Models/PackageModification/DownloadPackageVersionStep.cs

@ -9,8 +9,11 @@ public class DownloadPackageVersionStep : IPackageStep
private readonly string installPath;
private readonly DownloadPackageVersionOptions downloadOptions;
public DownloadPackageVersionStep(BasePackage package, string installPath,
DownloadPackageVersionOptions downloadOptions)
public DownloadPackageVersionStep(
BasePackage package,
string installPath,
DownloadPackageVersionOptions downloadOptions
)
{
this.package = package;
this.installPath = installPath;

6
StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs

@ -20,11 +20,7 @@ public class InstallPackageStep : IPackageStep
{
package.ConsoleOutput += (sender, output) =>
{
progress?.Report(new ProgressReport
{
IsIndeterminate = true,
Message = output.Text
});
progress?.Report(new ProgressReport { IsIndeterminate = true, Message = output.Text });
};
await package.InstallPackage(installPath, torchVersion, progress).ConfigureAwait(false);
}

1
StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs

@ -27,5 +27,6 @@ public class PackageModificationRunner : IPackageModificationRunner
public IPackageStep? CurrentStep { get; set; }
public event EventHandler<ProgressReport>? ProgressChanged;
protected virtual void OnProgressChanged(ProgressReport e) => ProgressChanged?.Invoke(this, e);
}

12
StabilityMatrix.Core/Models/PackageModification/SetupModelFoldersStep.cs

@ -9,8 +9,11 @@ public class SetupModelFoldersStep : IPackageStep
private readonly SharedFolderMethod sharedFolderMethod;
private readonly string installPath;
public SetupModelFoldersStep(BasePackage package, SharedFolderMethod sharedFolderMethod,
string installPath)
public SetupModelFoldersStep(
BasePackage package,
SharedFolderMethod sharedFolderMethod,
string installPath
)
{
this.package = package;
this.sharedFolderMethod = sharedFolderMethod;
@ -19,8 +22,9 @@ public class SetupModelFoldersStep : IPackageStep
public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)
{
progress?.Report(new ProgressReport(-1f, "Setting up shared folder links...",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Setting up shared folder links...", isIndeterminate: true)
);
await package.SetupModelFolders(installPath, sharedFolderMethod).ConfigureAwait(false);
}

5
StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs

@ -23,8 +23,9 @@ public class SetupPrerequisitesStep : IPackageStep
// python stuff
if (!PyRunner.PipInstalled || !PyRunner.VenvInstalled)
{
progress?.Report(new ProgressReport(-1f, "Installing Python prerequisites...",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Installing Python prerequisites...", isIndeterminate: true)
);
await pyRunner.Initialize().ConfigureAwait(false);

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

@ -28,114 +28,112 @@ public class A3WebUI : BaseGitPackage
new("https://github.com/AUTOMATIC1111/stable-diffusion-webui/raw/master/screenshot.png");
public string RelativeArgsDefinitionScriptPath => "modules.cmd_args";
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Symlink;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public A3WebUI(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper) :
base(githubApi, settingsManager, downloadService, prerequisiteHelper)
{
}
public A3WebUI(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
// From https://github.com/AUTOMATIC1111/stable-diffusion-webui/tree/master/models
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders => new()
{
[SharedFolderType.StableDiffusion] = new[] {"models/Stable-diffusion"},
[SharedFolderType.ESRGAN] = new[] {"models/ESRGAN"},
[SharedFolderType.RealESRGAN] = new[] {"models/RealESRGAN"},
[SharedFolderType.SwinIR] = new[] {"models/SwinIR"},
[SharedFolderType.Lora] = new[] {"models/Lora"},
[SharedFolderType.LyCORIS] = new[] {"models/LyCORIS"},
[SharedFolderType.ApproxVAE] = new[] {"models/VAE-approx"},
[SharedFolderType.VAE] = new[] {"models/VAE"},
[SharedFolderType.DeepDanbooru] = new[] {"models/deepbooru"},
[SharedFolderType.Karlo] = new[] {"models/karlo"},
[SharedFolderType.TextualInversion] = new[] {"embeddings"},
[SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"},
[SharedFolderType.ControlNet] = new[] {"models/ControlNet"}
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
public override List<LaunchOptionDefinition> LaunchOptions => new()
{
new()
{
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
Options = new() {"--host"}
},
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
Options = new() {"--port"}
},
[SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" },
[SharedFolderType.ESRGAN] = new[] { "models/ESRGAN" },
[SharedFolderType.RealESRGAN] = new[] { "models/RealESRGAN" },
[SharedFolderType.SwinIR] = new[] { "models/SwinIR" },
[SharedFolderType.Lora] = new[] { "models/Lora" },
[SharedFolderType.LyCORIS] = new[] { "models/LyCORIS" },
[SharedFolderType.ApproxVAE] = new[] { "models/VAE-approx" },
[SharedFolderType.VAE] = new[] { "models/VAE" },
[SharedFolderType.DeepDanbooru] = new[] { "models/deepbooru" },
[SharedFolderType.Karlo] = new[] { "models/karlo" },
[SharedFolderType.TextualInversion] = new[] { "embeddings" },
[SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" },
[SharedFolderType.ControlNet] = new[] { "models/ControlNet" }
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
public override List<LaunchOptionDefinition> LaunchOptions =>
new()
{
Name = "VRAM",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch
new()
{
Level.Low => "--lowvram",
Level.Medium => "--medvram",
_ => null
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
Options = new() { "--host" }
},
Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" }
},
new()
{
Name = "Xformers",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasNvidiaGpu(),
Options = new() { "--xformers" }
},
new()
{
Name = "API",
Type = LaunchOptionType.Bool,
InitialValue = true,
Options = new() {"--api"}
},
new()
{
Name = "Skip Torch CUDA Check",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
Options = new() {"--skip-torch-cuda-test"}
},
new()
{
Name = "Skip Python Version Check",
Type = LaunchOptionType.Bool,
InitialValue = true,
Options = new() {"--skip-python-version-check"}
},
new()
{
Name = "No Half",
Type = LaunchOptionType.Bool,
Description = "Do not switch the model to 16-bit floats",
InitialValue = HardwareHelper.HasAmdGpu(),
Options = new() {"--no-half"}
},
LaunchOptionDefinition.Extras
};
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => new[]
{
SharedFolderMethod.Symlink,
SharedFolderMethod.None
};
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
Options = new() { "--port" }
},
new()
{
Name = "VRAM",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper
.IterGpuInfo()
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--medvram",
_ => null
},
Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" }
},
new()
{
Name = "Xformers",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasNvidiaGpu(),
Options = new() { "--xformers" }
},
new()
{
Name = "API",
Type = LaunchOptionType.Bool,
InitialValue = true,
Options = new() { "--api" }
},
new()
{
Name = "Skip Torch CUDA Check",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
Options = new() { "--skip-torch-cuda-test" }
},
new()
{
Name = "Skip Python Version Check",
Type = LaunchOptionType.Bool,
InitialValue = true,
Options = new() { "--skip-python-version-check" }
},
new()
{
Name = "No Half",
Type = LaunchOptionType.Bool,
Description = "Do not switch the model to 16-bit floats",
InitialValue = HardwareHelper.HasAmdGpu(),
Options = new() { "--no-half" }
},
LaunchOptionDefinition.Extras
};
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[]
{
TorchVersion.Cpu,
TorchVersion.Cuda,
TorchVersion.DirectMl,
TorchVersion.Rocm
};
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };
public override IEnumerable<TorchVersion> AvailableTorchVersions =>
new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Rocm };
public override async Task<string> GetLatestVersion()
{
@ -143,8 +141,11 @@ public class A3WebUI : BaseGitPackage
return release.TagName!;
}
public override async Task InstallPackage(string installLocation,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null)
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
@ -173,14 +174,17 @@ public class A3WebUI : BaseGitPackage
}
// Install requirements file
progress?.Report(new ProgressReport(-1f, "Installing Package Requirements",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Installing Package Requirements", isIndeterminate: true)
);
Logger.Info("Installing requirements_versions.txt");
await venvRunner.PipInstall($"-r requirements_versions.txt", OnConsoleOutput)
await venvRunner
.PipInstall($"-r requirements_versions.txt", OnConsoleOutput)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, "Installing Package Requirements",
isIndeterminate: false));
progress?.Report(
new ProgressReport(1f, "Installing Package Requirements", isIndeterminate: false)
);
progress?.Report(new ProgressReport(-1f, "Updating configuration", isIndeterminate: true));
@ -189,14 +193,18 @@ public class A3WebUI : BaseGitPackage
var configPath = Path.Combine(installLocation, "config.json");
if (!File.Exists(configPath))
{
var config = new JsonObject {{"show_progress_type", "TAESD"}};
var config = new JsonObject { { "show_progress_type", "TAESD" } };
await File.WriteAllTextAsync(configPath, config.ToString()).ConfigureAwait(false);
}
progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false));
}
public override async Task RunPackage(string installedPackagePath, string command, string arguments)
public override async Task RunPackage(
string installedPackagePath,
string command,
string arguments
)
{
await SetupVenv(installedPackagePath).ConfigureAwait(false);
@ -221,16 +229,19 @@ public class A3WebUI : BaseGitPackage
VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit);
}
private async Task InstallRocmTorch(PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null)
private async Task InstallRocmTorch(
PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true)
);
await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput)
.ConfigureAwait(false);
await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput).ConfigureAwait(false);
await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsRocm511, OnConsoleOutput)
await venvRunner
.PipInstall(PyVenvRunner.TorchPipInstallArgsRocm511, OnConsoleOutput)
.ConfigureAwait(false);
}
}

160
StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs

@ -47,21 +47,23 @@ public abstract class BaseGitPackage : BasePackage
if (!string.IsNullOrWhiteSpace(versionOptions.VersionTag))
{
return
$"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.VersionTag}";
return $"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.VersionTag}";
}
if (!string.IsNullOrWhiteSpace(versionOptions.BranchName))
{
return
$"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.BranchName}";
return $"https://api.github.com/repos/{Author}/{Name}/zipball/{versionOptions.BranchName}";
}
throw new Exception("No download URL available");
}
protected BaseGitPackage(IGithubApiCache githubApi, ISettingsManager settingsManager,
IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper)
protected BaseGitPackage(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
)
{
GithubApi = githubApi;
SettingsManager = settingsManager;
@ -71,9 +73,7 @@ public abstract class BaseGitPackage : BasePackage
protected async Task<Release> GetLatestRelease(bool includePrerelease = false)
{
var releases = await GithubApi
.GetAllReleases(Author, Name)
.ConfigureAwait(false);
var releases = await GithubApi.GetAllReleases(Author, Name).ConfigureAwait(false);
return includePrerelease ? releases.First() : releases.First(x => !x.Prerelease);
}
@ -87,7 +87,11 @@ public abstract class BaseGitPackage : BasePackage
return GithubApi.GetAllReleases(Author, Name);
}
public override Task<IEnumerable<GitCommit>?> GetAllCommits(string branch, int page = 1, int perPage = 10)
public override Task<IEnumerable<GitCommit>?> GetAllCommits(
string branch,
int page = 1,
int perPage = 10
)
{
return GithubApi.GetAllCommits(Author, Name, branch, page, perPage);
}
@ -100,22 +104,22 @@ public abstract class BaseGitPackage : BasePackage
var releasesList = allReleases.ToList();
if (releasesList.Any())
{
packageVersionOptions.AvailableVersions = releasesList.Select(r =>
new PackageVersion
{
TagName = r.TagName!,
ReleaseNotesMarkdown = r.Body,
IsPrerelease = r.Prerelease
});
packageVersionOptions.AvailableVersions = releasesList.Select(
r =>
new PackageVersion
{
TagName = r.TagName!,
ReleaseNotesMarkdown = r.Body,
IsPrerelease = r.Prerelease
}
);
}
// Branch mode
var allBranches = await GetAllBranches().ConfigureAwait(false);
packageVersionOptions.AvailableBranches = allBranches.Select(b => new PackageVersion
{
TagName = $"{b.Name}",
ReleaseNotesMarkdown = string.Empty
});
packageVersionOptions.AvailableBranches = allBranches.Select(
b => new PackageVersion { TagName = $"{b.Name}", ReleaseNotesMarkdown = string.Empty }
);
return packageVersionOptions;
}
@ -127,7 +131,8 @@ public abstract class BaseGitPackage : BasePackage
public async Task<PyVenvRunner> SetupVenv(
string installedPackagePath,
string venvName = "venv",
bool forceRecreate = false)
bool forceRecreate = false
)
{
var venvPath = Path.Combine(installedPackagePath, venvName);
if (VenvRunner != null)
@ -150,14 +155,15 @@ public abstract class BaseGitPackage : BasePackage
public override async Task<IEnumerable<Release>> GetReleaseTags()
{
var allReleases = await GithubApi
.GetAllReleases(Author, Name)
.ConfigureAwait(false);
var allReleases = await GithubApi.GetAllReleases(Author, Name).ConfigureAwait(false);
return allReleases;
}
public override async Task DownloadPackage(string installLocation,
DownloadPackageVersionOptions versionOptions, IProgress<ProgressReport>? progress = null)
public override async Task DownloadPackage(
string installLocation,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null
)
{
var downloadUrl = GetDownloadUrl(versionOptions);
@ -173,8 +179,11 @@ public abstract class BaseGitPackage : BasePackage
progress?.Report(new ProgressReport(100, message: "Download Complete"));
}
public override async Task InstallPackage(string installLocation,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null)
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
await UnzipPackage(installLocation, progress).ConfigureAwait(false);
File.Delete(DownloadLocation);
@ -197,19 +206,25 @@ public abstract class BaseGitPackage : BasePackage
zipDirName = entry.FullName;
}
var folderPath = Path.Combine(installLocation,
entry.FullName.Replace(zipDirName, string.Empty));
var folderPath = Path.Combine(
installLocation,
entry.FullName.Replace(zipDirName, string.Empty)
);
Directory.CreateDirectory(folderPath);
continue;
}
var destinationPath = Path.GetFullPath(Path.Combine(installLocation,
entry.FullName.Replace(zipDirName, string.Empty)));
var destinationPath = Path.GetFullPath(
Path.Combine(installLocation, entry.FullName.Replace(zipDirName, string.Empty))
);
entry.ExtractToFile(destinationPath, true);
progress?.Report(new ProgressReport(current: Convert.ToUInt64(currentEntry),
total: Convert.ToUInt64(totalEntries)));
progress?.Report(
new ProgressReport(
current: Convert.ToUInt64(currentEntry),
total: Convert.ToUInt64(totalEntries)
)
);
}
return Task.CompletedTask;
@ -232,8 +247,9 @@ public abstract class BaseGitPackage : BasePackage
return UpdateAvailable;
}
var allCommits = (await GetAllCommits(currentVersion.InstalledBranch)
.ConfigureAwait(false))?.ToList();
var allCommits = (
await GetAllCommits(currentVersion.InstalledBranch).ConfigureAwait(false)
)?.ToList();
if (allCommits == null || !allCommits.Any())
{
Logger.Warn("No commits found for {Package}", package.PackageName);
@ -241,7 +257,6 @@ public abstract class BaseGitPackage : BasePackage
}
var latestCommitHash = allCommits.First().Sha;
return latestCommitHash != currentVersion.InstalledCommitSha;
}
catch (ApiException e)
{
@ -250,34 +265,37 @@ public abstract class BaseGitPackage : BasePackage
}
}
public override async Task<InstalledPackageVersion> Update(InstalledPackage installedPackage,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null,
bool includePrerelease = false)
public override async Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false
)
{
if (installedPackage.Version == null) throw new NullReferenceException("Version is null");
if (installedPackage.Version == null)
throw new NullReferenceException("Version is null");
if (installedPackage.Version.IsReleaseMode)
{
var releases = await GetAllReleases().ConfigureAwait(false);
var latestRelease = releases.First(x => includePrerelease || !x.Prerelease);
await DownloadPackage(installedPackage.FullPath,
new DownloadPackageVersionOptions {VersionTag = latestRelease.TagName},
progress)
await DownloadPackage(
installedPackage.FullPath,
new DownloadPackageVersionOptions { VersionTag = latestRelease.TagName },
progress
)
.ConfigureAwait(false);
await InstallPackage(installedPackage.FullPath, torchVersion, progress)
.ConfigureAwait(false);
return new InstalledPackageVersion
{
InstalledReleaseVersion = latestRelease.TagName
};
return new InstalledPackageVersion { InstalledReleaseVersion = latestRelease.TagName };
}
// Commit mode
var allCommits = await GetAllCommits(
installedPackage.Version.InstalledBranch).ConfigureAwait(false);
var allCommits = await GetAllCommits(installedPackage.Version.InstalledBranch)
.ConfigureAwait(false);
var latestCommit = allCommits?.First();
if (latestCommit is null || string.IsNullOrEmpty(latestCommit.Sha))
@ -285,8 +303,11 @@ public abstract class BaseGitPackage : BasePackage
throw new Exception("No commits found for branch");
}
await DownloadPackage(installedPackage.FullPath,
new DownloadPackageVersionOptions {CommitHash = latestCommit.Sha}, progress)
await DownloadPackage(
installedPackage.FullPath,
new DownloadPackageVersionOptions { CommitHash = latestCommit.Sha },
progress
)
.ConfigureAwait(false);
await InstallPackage(installedPackage.FullPath, torchVersion, progress)
.ConfigureAwait(false);
@ -298,29 +319,40 @@ public abstract class BaseGitPackage : BasePackage
};
}
public override Task SetupModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task SetupModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
if (sharedFolderMethod == SharedFolderMethod.Symlink && SharedFolders is { } folders)
{
StabilityMatrix.Core.Helper.SharedFolders
.SetupLinks(folders, SettingsManager.ModelsDirectory, installDirectory);
StabilityMatrix.Core.Helper.SharedFolders.SetupLinks(
folders,
SettingsManager.ModelsDirectory,
installDirectory
);
}
return Task.CompletedTask;
}
public override async Task UpdateModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override async Task UpdateModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
if (SharedFolders is not null && sharedFolderMethod == SharedFolderMethod.Symlink)
{
await StabilityMatrix.Core.Helper.SharedFolders.UpdateLinksForPackage(this,
SettingsManager.ModelsDirectory, installDirectory).ConfigureAwait(false);
await StabilityMatrix.Core.Helper.SharedFolders
.UpdateLinksForPackage(this, SettingsManager.ModelsDirectory, installDirectory)
.ConfigureAwait(false);
}
}
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod)
public override Task RemoveModelFolderLinks(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
if (SharedFolders is not null && sharedFolderMethod == SharedFolderMethod.Symlink)
{

122
StabilityMatrix.Core/Models/Packages/BasePackage.cs

@ -36,37 +36,53 @@ public abstract class BasePackage
public virtual bool ShouldIgnoreReleases => false;
public virtual bool UpdateAvailable { get; set; }
public abstract Task DownloadPackage(string installLocation, DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress1);
public abstract Task InstallPackage(string installLocation, TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null);
public abstract Task DownloadPackage(
string installLocation,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress1
);
public abstract Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
);
public abstract Task RunPackage(string installedPackagePath, string command, string arguments);
public abstract Task<bool> CheckForUpdates(InstalledPackage package);
public abstract Task<InstalledPackageVersion> Update(InstalledPackage installedPackage,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null,
bool includePrerelease = false);
public abstract Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false
);
public virtual IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => new[]
{
SharedFolderMethod.Symlink,
SharedFolderMethod.Configuration,
SharedFolderMethod.None
};
public virtual IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
new[]
{
SharedFolderMethod.Symlink,
SharedFolderMethod.Configuration,
SharedFolderMethod.None
};
public abstract SharedFolderMethod RecommendedSharedFolderMethod { get; }
public abstract Task SetupModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod);
public abstract Task SetupModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
);
public abstract Task UpdateModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod);
public abstract Task UpdateModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
);
public abstract Task RemoveModelFolderLinks(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod);
public abstract Task RemoveModelFolderLinks(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
);
public abstract IEnumerable<TorchVersion> AvailableTorchVersions { get; }
@ -88,8 +104,10 @@ public abstract class BasePackage
return TorchVersion.Rocm;
}
if (HardwareHelper.PreferDirectML() &&
AvailableTorchVersions.Contains(TorchVersion.DirectMl))
if (
HardwareHelper.PreferDirectML()
&& AvailableTorchVersions.Contains(TorchVersion.DirectMl)
)
{
return TorchVersion.DirectMl;
}
@ -119,7 +137,11 @@ public abstract class BasePackage
public abstract Task<string> GetLatestVersion();
public abstract Task<PackageVersionOptions> GetAllVersionOptions();
public abstract Task<IEnumerable<GitCommit>?> GetAllCommits(string branch, int page = 1, int perPage = 10);
public abstract Task<IEnumerable<GitCommit>?> GetAllCommits(
string branch,
int page = 1,
int perPage = 10
);
public abstract Task<IEnumerable<Branch>> GetAllBranches();
public abstract Task<IEnumerable<Release>> GetAllReleases();
public event EventHandler<ProcessOutput>? ConsoleOutput;
@ -127,44 +149,56 @@ public abstract class BasePackage
public event EventHandler<string>? StartupComplete;
public void OnConsoleOutput(ProcessOutput output) => ConsoleOutput?.Invoke(this, output);
public void OnExit(int exitCode) => Exited?.Invoke(this, exitCode);
public void OnStartupComplete(string url) => StartupComplete?.Invoke(this, url);
public virtual PackageVersionType AvailableVersionTypes => ShouldIgnoreReleases
? PackageVersionType.Commit
: PackageVersionType.GithubRelease | PackageVersionType.Commit;
public void OnExit(int exitCode) => Exited?.Invoke(this, exitCode);
public void OnStartupComplete(string url) => StartupComplete?.Invoke(this, url);
public virtual PackageVersionType AvailableVersionTypes =>
ShouldIgnoreReleases
? PackageVersionType.Commit
: PackageVersionType.GithubRelease | PackageVersionType.Commit;
protected async Task InstallCudaTorch(PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null)
protected async Task InstallCudaTorch(
PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for CUDA",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Installing PyTorch for CUDA", isIndeterminate: true)
);
await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsCuda, OnConsoleOutput)
await venvRunner
.PipInstall(PyVenvRunner.TorchPipInstallArgsCuda, OnConsoleOutput)
.ConfigureAwait(false);
await venvRunner.PipInstall("xformers", OnConsoleOutput).ConfigureAwait(false);
}
protected async Task InstallDirectMlTorch(PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null)
protected async Task InstallDirectMlTorch(
PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for DirectML",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Installing PyTorch for DirectML", isIndeterminate: true)
);
await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsDirectML, OnConsoleOutput)
await venvRunner
.PipInstall(PyVenvRunner.TorchPipInstallArgsDirectML, OnConsoleOutput)
.ConfigureAwait(false);
}
protected async Task InstallCpuTorch(PyVenvRunner venvRunner, IProgress<ProgressReport>? progress = null)
protected async Task InstallCpuTorch(
PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for CPU",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Installing PyTorch for CPU", isIndeterminate: true)
);
await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsCpu, OnConsoleOutput)
await venvRunner
.PipInstall(PyVenvRunner.TorchPipInstallArgsCpu, OnConsoleOutput)
.ConfigureAwait(false);
}
}

217
StabilityMatrix.Core/Models/Packages/ComfyUI.cs

@ -32,77 +32,82 @@ public class ComfyUI : BaseGitPackage
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Configuration;
public ComfyUI(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper) :
base(githubApi, settingsManager, downloadService, prerequisiteHelper)
{
}
public ComfyUI(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
// https://github.com/comfyanonymous/ComfyUI/blob/master/folder_paths.py#L11
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders => new()
{
[SharedFolderType.StableDiffusion] = new[] {"models/checkpoints"},
[SharedFolderType.Diffusers] = new[] {"models/diffusers"},
[SharedFolderType.Lora] = new[] {"models/loras"},
[SharedFolderType.CLIP] = new[] {"models/clip"},
[SharedFolderType.TextualInversion] = new[] {"models/embeddings"},
[SharedFolderType.VAE] = new[] {"models/vae"},
[SharedFolderType.ApproxVAE] = new[] {"models/vae_approx"},
[SharedFolderType.ControlNet] = new[] {"models/controlnet"},
[SharedFolderType.GLIGEN] = new[] {"models/gligen"},
[SharedFolderType.ESRGAN] = new[] {"models/upscale_models"},
[SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"},
};
public override List<LaunchOptionDefinition> LaunchOptions => new List<LaunchOptionDefinition>
{
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>
new()
{
Name = "VRAM",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch
[SharedFolderType.StableDiffusion] = new[] { "models/checkpoints" },
[SharedFolderType.Diffusers] = new[] { "models/diffusers" },
[SharedFolderType.Lora] = new[] { "models/loras" },
[SharedFolderType.CLIP] = new[] { "models/clip" },
[SharedFolderType.TextualInversion] = new[] { "models/embeddings" },
[SharedFolderType.VAE] = new[] { "models/vae" },
[SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" },
[SharedFolderType.ControlNet] = new[] { "models/controlnet" },
[SharedFolderType.GLIGEN] = new[] { "models/gligen" },
[SharedFolderType.ESRGAN] = new[] { "models/upscale_models" },
[SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" },
};
public override List<LaunchOptionDefinition> LaunchOptions =>
new List<LaunchOptionDefinition>
{
new()
{
Level.Low => "--lowvram",
Level.Medium => "--normalvram",
_ => null
Name = "VRAM",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper
.IterGpuInfo()
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--normalvram",
_ => null
},
Options = { "--highvram", "--normalvram", "--lowvram", "--novram" }
},
Options = { "--highvram", "--normalvram", "--lowvram", "--novram" }
},
new()
{
Name = "Use CPU only",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
Options = {"--cpu"}
},
new()
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
Options = { "--disable-xformers" }
},
new()
{
Name = "Auto-Launch",
Type = LaunchOptionType.Bool,
Options = { "--auto-launch" }
},
LaunchOptionDefinition.Extras
};
new()
{
Name = "Use CPU only",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
Options = { "--cpu" }
},
new()
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
Options = { "--disable-xformers" }
},
new()
{
Name = "Auto-Launch",
Type = LaunchOptionType.Bool,
Options = { "--auto-launch" }
},
LaunchOptionDefinition.Extras
};
public override Task<string> GetLatestVersion() => Task.FromResult("master");
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[]
{
TorchVersion.Cpu,
TorchVersion.Cuda,
TorchVersion.DirectMl,
TorchVersion.Rocm
};
public override async Task InstallPackage(string installLocation,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null)
public override IEnumerable<TorchVersion> AvailableTorchVersions =>
new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Rocm };
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
@ -132,17 +137,21 @@ public class ComfyUI : BaseGitPackage
}
// Install requirements file
progress?.Report(new ProgressReport(-1, "Installing Package Requirements",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1, "Installing Package Requirements", isIndeterminate: true)
);
Logger.Info("Installing requirements.txt");
await venvRunner.PipInstall($"-r requirements.txt", OnConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(1, "Installing Package Requirements",
isIndeterminate: false));
progress?.Report(
new ProgressReport(1, "Installing Package Requirements", isIndeterminate: false)
);
}
private async Task AutoDetectAndInstallTorch(PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null)
private async Task AutoDetectAndInstallTorch(
PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null
)
{
var gpus = HardwareHelper.IterGpuInfo().ToList();
if (gpus.Any(g => g.IsNvidia))
@ -163,7 +172,11 @@ public class ComfyUI : BaseGitPackage
}
}
public override async Task RunPackage(string installedPackagePath, string command, string arguments)
public override async Task RunPackage(
string installedPackagePath,
string command,
string arguments
)
{
await SetupVenv(installedPackagePath).ConfigureAwait(false);
@ -191,14 +204,13 @@ public class ComfyUI : BaseGitPackage
var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}";
VenvRunner?.RunDetached(
args.TrimEnd(),
HandleConsoleOutput,
HandleExit);
VenvRunner?.RunDetached(args.TrimEnd(), HandleConsoleOutput, HandleExit);
}
public override Task SetupModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task SetupModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
switch (sharedFolderMethod)
{
@ -223,19 +235,22 @@ public class ComfyUI : BaseGitPackage
File.WriteAllText(extraPathsYamlPath, string.Empty);
}
var yaml = File.ReadAllText(extraPathsYamlPath);
var comfyModelPaths = deserializer.Deserialize<ComfyModelPathsYaml>(yaml) ??
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
// cuz it can actually be null lol
new ComfyModelPathsYaml();
var comfyModelPaths =
deserializer.Deserialize<ComfyModelPathsYaml>(yaml)
??
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
// cuz it can actually be null lol
new ComfyModelPathsYaml();
comfyModelPaths.StabilityMatrix ??= new ComfyModelPathsYaml.SmData();
comfyModelPaths.StabilityMatrix.Checkpoints = Path.Combine(modelsDir, "StableDiffusion");
comfyModelPaths.StabilityMatrix.Vae = Path.Combine(modelsDir, "VAE");
comfyModelPaths.StabilityMatrix.Loras = $"{Path.Combine(modelsDir, "Lora")}\n" +
$"{Path.Combine(modelsDir, "LyCORIS")}";
comfyModelPaths.StabilityMatrix.UpscaleModels = $"{Path.Combine(modelsDir, "ESRGAN")}\n" +
$"{Path.Combine(modelsDir, "RealESRGAN")}\n" +
$"{Path.Combine(modelsDir, "SwinIR")}";
comfyModelPaths.StabilityMatrix.Loras =
$"{Path.Combine(modelsDir, "Lora")}\n" + $"{Path.Combine(modelsDir, "LyCORIS")}";
comfyModelPaths.StabilityMatrix.UpscaleModels =
$"{Path.Combine(modelsDir, "ESRGAN")}\n"
+ $"{Path.Combine(modelsDir, "RealESRGAN")}\n"
+ $"{Path.Combine(modelsDir, "SwinIR")}";
comfyModelPaths.StabilityMatrix.Embeddings = Path.Combine(modelsDir, "TextualInversion");
comfyModelPaths.StabilityMatrix.Hypernetworks = Path.Combine(modelsDir, "Hypernetwork");
comfyModelPaths.StabilityMatrix.Controlnet = Path.Combine(modelsDir, "ControlNet");
@ -253,33 +268,39 @@ public class ComfyUI : BaseGitPackage
return Task.CompletedTask;
}
public override Task UpdateModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod) =>
SetupModelFolders(installDirectory, sharedFolderMethod);
public override Task UpdateModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
) => SetupModelFolders(installDirectory, sharedFolderMethod);
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task RemoveModelFolderLinks(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
return sharedFolderMethod switch
{
SharedFolderMethod.Configuration => Task.CompletedTask,
SharedFolderMethod.None => Task.CompletedTask,
SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory,
sharedFolderMethod),
SharedFolderMethod.Symlink
=> base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod),
_ => Task.CompletedTask
};
}
private async Task InstallRocmTorch(PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null)
private async Task InstallRocmTorch(
PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true)
);
await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput)
.ConfigureAwait(false);
await venvRunner.PipInstall("--upgrade pip wheel", OnConsoleOutput).ConfigureAwait(false);
await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsRocm542, OnConsoleOutput)
await venvRunner
.PipInstall(PyVenvRunner.TorchPipInstallArgsRocm542, OnConsoleOutput)
.ConfigureAwait(false);
}

39
StabilityMatrix.Core/Models/Packages/DankDiffusion.cs

@ -7,11 +7,13 @@ namespace StabilityMatrix.Core.Models.Packages;
public class DankDiffusion : BaseGitPackage
{
public DankDiffusion(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper) :
base(githubApi, settingsManager, downloadService, prerequisiteHelper)
{
}
public DankDiffusion(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
public override string Name => "dank-diffusion";
public override string DisplayName { get; set; } = "Dank Diffusion";
@ -21,13 +23,9 @@ public class DankDiffusion : BaseGitPackage
"https://github.com/LykosAI/StabilityMatrix/blob/main/LICENSE";
public override string Blurb => "A dank interface for diffusion";
public override string LaunchCommand => "test";
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Symlink;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public override IReadOnlyList<string> ExtraLaunchCommands => new[]
{
"test-config",
};
public override IReadOnlyList<string> ExtraLaunchCommands => new[] { "test-config", };
public override Uri PreviewImageUri { get; }
@ -36,20 +34,26 @@ public class DankDiffusion : BaseGitPackage
throw new NotImplementedException();
}
public override Task SetupModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task SetupModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
throw new NotImplementedException();
}
public override Task UpdateModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task UpdateModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
throw new NotImplementedException();
}
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task RemoveModelFolderLinks(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
throw new NotImplementedException();
}
@ -57,6 +61,7 @@ public class DankDiffusion : BaseGitPackage
public override IEnumerable<TorchVersion> AvailableTorchVersions { get; }
public override List<LaunchOptionDefinition> LaunchOptions { get; }
public override Task<string> GetLatestVersion()
{
throw new NotImplementedException();

151
StabilityMatrix.Core/Models/Packages/Fooocus.cs

@ -10,11 +10,13 @@ namespace StabilityMatrix.Core.Models.Packages;
public class Fooocus : BaseGitPackage
{
public Fooocus(IGithubApiCache githubApi, ISettingsManager settingsManager,
IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper) : base(githubApi,
settingsManager, downloadService, prerequisiteHelper)
{
}
public Fooocus(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
public override string Name => "Fooocus";
public override string DisplayName { get; set; } = "Fooocus";
@ -28,64 +30,60 @@ public class Fooocus : BaseGitPackage
public override string LaunchCommand => "launch.py";
public override Uri PreviewImageUri =>
new("https://user-images.githubusercontent.com/19834515/261830306-f79c5981-cf80-4ee3-b06b-3fef3f8bfbc7.png");
new(
"https://user-images.githubusercontent.com/19834515/261830306-f79c5981-cf80-4ee3-b06b-3fef3f8bfbc7.png"
);
public override List<LaunchOptionDefinition> LaunchOptions => new()
{
new LaunchOptionDefinition
{
Name = "Port",
Type = LaunchOptionType.String,
Description = "Sets the listen port",
Options = {"--port"}
},
new LaunchOptionDefinition
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
Options = {"--share"}
},
new LaunchOptionDefinition
public override List<LaunchOptionDefinition> LaunchOptions =>
new()
{
Name = "Listen",
Type = LaunchOptionType.String,
Description = "Set the listen interface",
Options = {"--listen"}
},
LaunchOptionDefinition.Extras
};
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Symlink;
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => new[]
{
SharedFolderMethod.Symlink,
SharedFolderMethod.None
};
new LaunchOptionDefinition
{
Name = "Port",
Type = LaunchOptionType.String,
Description = "Sets the listen port",
Options = { "--port" }
},
new LaunchOptionDefinition
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
Options = { "--share" }
},
new LaunchOptionDefinition
{
Name = "Listen",
Type = LaunchOptionType.String,
Description = "Set the listen interface",
Options = { "--listen" }
},
LaunchOptionDefinition.Extras
};
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders => new()
{
[SharedFolderType.StableDiffusion] = new[] {"models/checkpoints"},
[SharedFolderType.Diffusers] = new[] {"models/diffusers"},
[SharedFolderType.Lora] = new[] {"models/loras"},
[SharedFolderType.CLIP] = new[] {"models/clip"},
[SharedFolderType.TextualInversion] = new[] {"models/embeddings"},
[SharedFolderType.VAE] = new[] {"models/vae"},
[SharedFolderType.ApproxVAE] = new[] {"models/vae_approx"},
[SharedFolderType.ControlNet] = new[] {"models/controlnet"},
[SharedFolderType.GLIGEN] = new[] {"models/gligen"},
[SharedFolderType.ESRGAN] = new[] {"models/upscale_models"},
[SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"}
};
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[]
{
TorchVersion.Cpu,
TorchVersion.Cuda,
TorchVersion.Rocm
};
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>
new()
{
[SharedFolderType.StableDiffusion] = new[] { "models/checkpoints" },
[SharedFolderType.Diffusers] = new[] { "models/diffusers" },
[SharedFolderType.Lora] = new[] { "models/loras" },
[SharedFolderType.CLIP] = new[] { "models/clip" },
[SharedFolderType.TextualInversion] = new[] { "models/embeddings" },
[SharedFolderType.VAE] = new[] { "models/vae" },
[SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" },
[SharedFolderType.ControlNet] = new[] { "models/controlnet" },
[SharedFolderType.GLIGEN] = new[] { "models/gligen" },
[SharedFolderType.ESRGAN] = new[] { "models/upscale_models" },
[SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }
};
public override IEnumerable<TorchVersion> AvailableTorchVersions =>
new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.Rocm };
public override async Task<string> GetLatestVersion()
{
@ -93,11 +91,15 @@ public class Fooocus : BaseGitPackage
return release.TagName!;
}
public override async Task InstallPackage(string installLocation,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null)
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false);
var venvRunner = await SetupVenv(installLocation, forceRecreate: true)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true));
@ -120,15 +122,23 @@ public class Fooocus : BaseGitPackage
await venvRunner
.PipInstall(
$"torch==2.0.1 torchvision==0.15.2 --extra-index-url https://download.pytorch.org/whl/{torchVersionStr}",
OnConsoleOutput).ConfigureAwait(false);
OnConsoleOutput
)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing requirements...",
isIndeterminate: true));
await venvRunner.PipInstall("-r requirements_versions.txt", OnConsoleOutput)
progress?.Report(
new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true)
);
await venvRunner
.PipInstall("-r requirements_versions.txt", OnConsoleOutput)
.ConfigureAwait(false);
}
public override async Task RunPackage(string installedPackagePath, string command, string arguments)
public override async Task RunPackage(
string installedPackagePath,
string command,
string arguments
)
{
await SetupVenv(installedPackagePath).ConfigureAwait(false);
@ -156,9 +166,6 @@ public class Fooocus : BaseGitPackage
var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}";
VenvRunner?.RunDetached(
args.TrimEnd(),
HandleConsoleOutput,
HandleExit);
VenvRunner?.RunDetached(args.TrimEnd(), HandleConsoleOutput, HandleExit);
}
}

252
StabilityMatrix.Core/Models/Packages/InvokeAI.cs

@ -21,114 +21,112 @@ public class InvokeAI : BaseGitPackage
public override string Author => "invoke-ai";
public override string LicenseType => "Apache-2.0";
public override string LicenseUrl =>
"https://github.com/invoke-ai/InvokeAI/blob/main/LICENSE";
public override string LicenseUrl => "https://github.com/invoke-ai/InvokeAI/blob/main/LICENSE";
public override string Blurb => "Professional Creative Tools for Stable Diffusion";
public override string LaunchCommand => "invokeai-web";
public override IReadOnlyList<string> ExtraLaunchCommands => new[]
{
"invokeai-configure",
"invokeai-merge",
"invokeai-metadata",
"invokeai-model-install",
"invokeai-node-cli",
"invokeai-ti",
"invokeai-update",
};
public override Uri PreviewImageUri => new(
"https://raw.githubusercontent.com/invoke-ai/InvokeAI/main/docs/assets/canvas_preview.png");
public override IReadOnlyList<string> ExtraLaunchCommands =>
new[]
{
"invokeai-configure",
"invokeai-merge",
"invokeai-metadata",
"invokeai-model-install",
"invokeai-node-cli",
"invokeai-ti",
"invokeai-update",
};
public override Uri PreviewImageUri =>
new(
"https://raw.githubusercontent.com/invoke-ai/InvokeAI/main/docs/assets/canvas_preview.png"
);
public override bool ShouldIgnoreReleases => true;
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => new[]
{
SharedFolderMethod.Symlink,
SharedFolderMethod.None
};
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Symlink;
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public InvokeAI(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper) :
base(githubApi, settingsManager, downloadService, prerequisiteHelper)
{
}
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders => new()
{
[SharedFolderType.StableDiffusion] = new[] { RelativeRootPath + "/autoimport/main" },
[SharedFolderType.Lora] = new[] { RelativeRootPath + "/autoimport/lora" },
[SharedFolderType.TextualInversion] = new[] { RelativeRootPath + "/autoimport/embedding" },
[SharedFolderType.ControlNet] = new[] { RelativeRootPath + "/autoimport/controlnet" },
};
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
// https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CONFIGURATION.md
public override List<LaunchOptionDefinition> LaunchOptions => new List<LaunchOptionDefinition>
{
new()
{
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
Options = new List<string> {"--host"}
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "9090",
Options = new List<string> {"--port"}
},
new()
{
Name = "Allow Origins",
Description = "List of host names or IP addresses that are allowed to connect to the " +
"InvokeAI API in the format ['host1','host2',...]",
Type = LaunchOptionType.String,
DefaultValue = "[]",
Options = new List<string> {"--allow-origins"}
},
new()
{
Name = "Always use CPU",
Type = LaunchOptionType.Bool,
Options = new List<string> {"--always_use_cpu"}
},
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>
new()
{
Name = "Precision",
Type = LaunchOptionType.Bool,
Options = new List<string>
[SharedFolderType.StableDiffusion] = new[] { RelativeRootPath + "/autoimport/main" },
[SharedFolderType.Lora] = new[] { RelativeRootPath + "/autoimport/lora" },
[SharedFolderType.TextualInversion] = new[]
{
"--precision auto",
"--precision float16",
"--precision float32",
}
},
new()
RelativeRootPath + "/autoimport/embedding"
},
[SharedFolderType.ControlNet] = new[] { RelativeRootPath + "/autoimport/controlnet" },
};
// https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CONFIGURATION.md
public override List<LaunchOptionDefinition> LaunchOptions =>
new List<LaunchOptionDefinition>
{
Name = "Aggressively free up GPU memory after each operation",
Type = LaunchOptionType.Bool,
Options = new List<string> {"--free_gpu_mem"}
},
LaunchOptionDefinition.Extras
};
new()
{
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
Options = new List<string> { "--host" }
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "9090",
Options = new List<string> { "--port" }
},
new()
{
Name = "Allow Origins",
Description =
"List of host names or IP addresses that are allowed to connect to the "
+ "InvokeAI API in the format ['host1','host2',...]",
Type = LaunchOptionType.String,
DefaultValue = "[]",
Options = new List<string> { "--allow-origins" }
},
new()
{
Name = "Always use CPU",
Type = LaunchOptionType.Bool,
Options = new List<string> { "--always_use_cpu" }
},
new()
{
Name = "Precision",
Type = LaunchOptionType.Bool,
Options = new List<string>
{
"--precision auto",
"--precision float16",
"--precision float32",
}
},
new()
{
Name = "Aggressively free up GPU memory after each operation",
Type = LaunchOptionType.Bool,
Options = new List<string> { "--free_gpu_mem" }
},
LaunchOptionDefinition.Extras
};
public override Task<string> GetLatestVersion() => Task.FromResult("main");
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[]
{
TorchVersion.Cpu,
TorchVersion.Cuda,
TorchVersion.Rocm,
TorchVersion.Mps
};
public override IEnumerable<TorchVersion> AvailableTorchVersions =>
new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.Rocm, TorchVersion.Mps };
public override TorchVersion GetRecommendedTorchVersion()
{
@ -140,14 +138,20 @@ public class InvokeAI : BaseGitPackage
return base.GetRecommendedTorchVersion();
}
public override Task DownloadPackage(string installLocation,
DownloadPackageVersionOptions downloadOptions, IProgress<ProgressReport>? progress = null)
public override Task DownloadPackage(
string installLocation,
DownloadPackageVersionOptions downloadOptions,
IProgress<ProgressReport>? progress = null
)
{
return Task.CompletedTask;
}
public override async Task InstallPackage(string installLocation, TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null)
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
// Setup venv
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
@ -190,15 +194,23 @@ public class InvokeAI : BaseGitPackage
progress?.Report(new ProgressReport(-1f, "Configuring InvokeAI", isIndeterminate: true));
await RunInvokeCommand(installLocation, "invokeai-configure", "--yes --skip-sd-weights",
false).ConfigureAwait(false);
await RunInvokeCommand(
installLocation,
"invokeai-configure",
"--yes --skip-sd-weights",
false
)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false));
}
public override async Task<InstalledPackageVersion> Update(InstalledPackage installedPackage,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null,
bool includePrerelease = false)
public override async Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false
)
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
@ -207,7 +219,9 @@ public class InvokeAI : BaseGitPackage
throw new NullReferenceException("Installed package is missing Path and/or Version");
}
await using var venvRunner = new PyVenvRunner(Path.Combine(installedPackage.FullPath, "venv"));
await using var venvRunner = new PyVenvRunner(
Path.Combine(installedPackage.FullPath, "venv")
);
venvRunner.WorkingDirectory = installedPackage.FullPath;
venvRunner.EnvironmentVariables = GetEnvVars(installedPackage.FullPath);
@ -251,10 +265,7 @@ public class InvokeAI : BaseGitPackage
progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false));
return isReleaseMode
? new InstalledPackageVersion
{
InstalledReleaseVersion = latestVersion
}
? new InstalledPackageVersion { InstalledReleaseVersion = latestVersion }
: new InstalledPackageVersion
{
InstalledBranch = installedPackage.Version.InstalledBranch,
@ -262,11 +273,16 @@ public class InvokeAI : BaseGitPackage
};
}
public override Task
RunPackage(string installedPackagePath, string command, string arguments) =>
RunInvokeCommand(installedPackagePath, command, arguments, true);
public override Task RunPackage(
string installedPackagePath,
string command,
string arguments
) => RunInvokeCommand(installedPackagePath, command, arguments, true);
private async Task<string> GetUpdateVersion(InstalledPackage installedPackage, bool includePrerelease = false)
private async Task<string> GetUpdateVersion(
InstalledPackage installedPackage,
bool includePrerelease = false
)
{
if (installedPackage.Version == null)
throw new NullReferenceException("Installed package version is null");
@ -284,8 +300,12 @@ public class InvokeAI : BaseGitPackage
return latestCommit.Sha;
}
private async Task RunInvokeCommand(string installedPackagePath, string command,
string arguments, bool runDetached)
private async Task RunInvokeCommand(
string installedPackagePath,
string command,
string arguments,
bool runDetached
)
{
await SetupVenv(installedPackagePath).ConfigureAwait(false);
@ -304,7 +324,7 @@ public class InvokeAI : BaseGitPackage
// Split at ':' to get package and function
var split = entryPoint?.Split(':');
if (split is not {Length: > 1})
if (split is not { Length: > 1 })
{
throw new Exception($"Could not find entry point for InvokeAI: {entryPoint.ToRepr()}");
}
@ -338,16 +358,18 @@ public class InvokeAI : BaseGitPackage
OnStartupComplete(WebUrl);
}
VenvRunner.RunDetached($"-c \"{code}\" {arguments}".TrimEnd(), HandleConsoleOutput, OnExit);
VenvRunner.RunDetached(
$"-c \"{code}\" {arguments}".TrimEnd(),
HandleConsoleOutput,
OnExit
);
}
else
{
var result = await VenvRunner.Run($"-c \"{code}\" {arguments}".TrimEnd())
var result = await VenvRunner
.Run($"-c \"{code}\" {arguments}".TrimEnd())
.ConfigureAwait(false);
OnConsoleOutput(new ProcessOutput
{
Text = result.StandardOutput
});
OnConsoleOutput(new ProcessOutput { Text = result.StandardOutput });
}
}

74
StabilityMatrix.Core/Models/Packages/UnknownPackage.cs

@ -21,23 +21,25 @@ public class UnknownPackage : BasePackage
public override Uri PreviewImageUri => new("");
public override IReadOnlyList<string> ExtraLaunchCommands => new[]
{
"test-config",
};
public override IReadOnlyList<string> ExtraLaunchCommands => new[] { "test-config", };
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Symlink;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public override Task DownloadPackage(string installLocation, DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress1)
public override Task DownloadPackage(
string installLocation,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress1
)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task InstallPackage(string installLocation, TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null)
public override Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
throw new NotImplementedException();
}
@ -48,33 +50,34 @@ public class UnknownPackage : BasePackage
}
/// <inheritdoc />
public override Task SetupModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task SetupModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task UpdateModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task UpdateModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod)
public override Task RemoveModelFolderLinks(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
throw new NotImplementedException();
}
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[]
{
TorchVersion.Cuda,
TorchVersion.Cpu,
TorchVersion.Rocm,
TorchVersion.DirectMl
};
public override IEnumerable<TorchVersion> AvailableTorchVersions =>
new[] { TorchVersion.Cuda, TorchVersion.Cpu, TorchVersion.Rocm, TorchVersion.DirectMl };
/// <inheritdoc />
public override void Shutdown()
@ -95,28 +98,39 @@ public class UnknownPackage : BasePackage
}
/// <inheritdoc />
public override Task<InstalledPackageVersion> Update(InstalledPackage installedPackage,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null,
bool includePrerelease = false)
public override Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false
)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task<IEnumerable<Release>> GetReleaseTags() => Task.FromResult(Enumerable.Empty<Release>());
public override Task<IEnumerable<Release>> GetReleaseTags() =>
Task.FromResult(Enumerable.Empty<Release>());
public override List<LaunchOptionDefinition> LaunchOptions => new();
public override Task<string> GetLatestVersion() => Task.FromResult(string.Empty);
public override Task<PackageVersionOptions> GetAllVersionOptions() =>
Task.FromResult(new PackageVersionOptions());
/// <inheritdoc />
public override Task<IEnumerable<GitCommit>?> GetAllCommits(string branch, int page = 1, int perPage = 10) => Task.FromResult<IEnumerable<GitCommit>?>(null);
public override Task<IEnumerable<GitCommit>?> GetAllCommits(
string branch,
int page = 1,
int perPage = 10
) => Task.FromResult<IEnumerable<GitCommit>?>(null);
/// <inheritdoc />
public override Task<IEnumerable<Branch>> GetAllBranches() => Task.FromResult(Enumerable.Empty<Branch>());
public override Task<IEnumerable<Branch>> GetAllBranches() =>
Task.FromResult(Enumerable.Empty<Branch>());
/// <inheritdoc />
public override Task<IEnumerable<Release>> GetAllReleases() => Task.FromResult(Enumerable.Empty<Release>());
public override Task<IEnumerable<Release>> GetAllReleases() =>
Task.FromResult(Enumerable.Empty<Release>());
}

379
StabilityMatrix.Core/Models/Packages/VladAutomatic.cs

@ -31,128 +31,132 @@ public class VladAutomatic : BaseGitPackage
new("https://github.com/vladmandic/automatic/raw/master/html/black-orange.jpg");
public override bool ShouldIgnoreReleases => true;
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Symlink;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[]
{
TorchVersion.Cpu,
TorchVersion.Rocm,
TorchVersion.DirectMl,
TorchVersion.Cuda
};
public VladAutomatic(IGithubApiCache githubApi, ISettingsManager settingsManager, IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper) :
base(githubApi, settingsManager, downloadService, prerequisiteHelper)
{
}
public override IEnumerable<TorchVersion> AvailableTorchVersions =>
new[] { TorchVersion.Cpu, TorchVersion.Rocm, TorchVersion.DirectMl, TorchVersion.Cuda };
// https://github.com/vladmandic/automatic/blob/master/modules/shared.py#L324
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders => new()
{
[SharedFolderType.StableDiffusion] = new[] {"models/Stable-diffusion"},
[SharedFolderType.Diffusers] = new[] {"models/Diffusers"},
[SharedFolderType.VAE] = new[] {"models/VAE"},
[SharedFolderType.TextualInversion] = new[] {"models/embeddings"},
[SharedFolderType.Hypernetwork] = new[] {"models/hypernetworks"},
[SharedFolderType.Codeformer] = new[] {"models/Codeformer"},
[SharedFolderType.GFPGAN] = new[] {"models/GFPGAN"},
[SharedFolderType.BSRGAN] = new[] {"models/BSRGAN"},
[SharedFolderType.ESRGAN] = new[] {"models/ESRGAN"},
[SharedFolderType.RealESRGAN] = new[] {"models/RealESRGAN"},
[SharedFolderType.ScuNET] = new[] {"models/ScuNET"},
[SharedFolderType.SwinIR] = new[] {"models/SwinIR"},
[SharedFolderType.LDSR] = new[] {"models/LDSR"},
[SharedFolderType.CLIP] = new[] {"models/CLIP"},
[SharedFolderType.Lora] = new[] {"models/Lora"},
[SharedFolderType.LyCORIS] = new[] {"models/LyCORIS"},
[SharedFolderType.ControlNet] = new[] {"models/ControlNet"}
};
public VladAutomatic(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
public override List<LaunchOptionDefinition> LaunchOptions => new()
{
new()
{
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
Options = new() {"--server-name"}
},
// https://github.com/vladmandic/automatic/blob/master/modules/shared.py#L324
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
Options = new() {"--port"}
},
[SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" },
[SharedFolderType.Diffusers] = new[] { "models/Diffusers" },
[SharedFolderType.VAE] = new[] { "models/VAE" },
[SharedFolderType.TextualInversion] = new[] { "models/embeddings" },
[SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" },
[SharedFolderType.Codeformer] = new[] { "models/Codeformer" },
[SharedFolderType.GFPGAN] = new[] { "models/GFPGAN" },
[SharedFolderType.BSRGAN] = new[] { "models/BSRGAN" },
[SharedFolderType.ESRGAN] = new[] { "models/ESRGAN" },
[SharedFolderType.RealESRGAN] = new[] { "models/RealESRGAN" },
[SharedFolderType.ScuNET] = new[] { "models/ScuNET" },
[SharedFolderType.SwinIR] = new[] { "models/SwinIR" },
[SharedFolderType.LDSR] = new[] { "models/LDSR" },
[SharedFolderType.CLIP] = new[] { "models/CLIP" },
[SharedFolderType.Lora] = new[] { "models/Lora" },
[SharedFolderType.LyCORIS] = new[] { "models/LyCORIS" },
[SharedFolderType.ControlNet] = new[] { "models/ControlNet" }
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
public override List<LaunchOptionDefinition> LaunchOptions =>
new()
{
Name = "VRAM",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch
new()
{
Level.Low => "--lowvram",
Level.Medium => "--medvram",
_ => null
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
Options = new() { "--server-name" }
},
Options = new() { "--lowvram", "--medvram" }
},
new()
{
Name = "Force use of Intel OneAPI XPU backend",
Type = LaunchOptionType.Bool,
Options = new() { "--use-ipex" }
},
new()
{
Name = "Use DirectML if no compatible GPU is detected",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.PreferDirectML(),
Options = new() { "--use-directml" }
},
new()
{
Name = "Force use of Nvidia CUDA backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasNvidiaGpu(),
Options = new() { "--use-cuda" }
},
new()
{
Name = "Force use of AMD ROCm backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.PreferRocm(),
Options = new() { "--use-rocm" }
},
new()
{
Name = "CUDA Device ID",
Type = LaunchOptionType.String,
Options = new() { "--device-id" }
},
new()
{
Name = "API",
Type = LaunchOptionType.Bool,
Options = new() { "--api" }
},
new()
{
Name = "Debug Logging",
Type = LaunchOptionType.Bool,
Options = new() { "--debug" }
},
LaunchOptionDefinition.Extras
};
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
Options = new() { "--port" }
},
new()
{
Name = "VRAM",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper
.IterGpuInfo()
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--medvram",
_ => null
},
Options = new() { "--lowvram", "--medvram" }
},
new()
{
Name = "Force use of Intel OneAPI XPU backend",
Type = LaunchOptionType.Bool,
Options = new() { "--use-ipex" }
},
new()
{
Name = "Use DirectML if no compatible GPU is detected",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.PreferDirectML(),
Options = new() { "--use-directml" }
},
new()
{
Name = "Force use of Nvidia CUDA backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasNvidiaGpu(),
Options = new() { "--use-cuda" }
},
new()
{
Name = "Force use of AMD ROCm backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.PreferRocm(),
Options = new() { "--use-rocm" }
},
new()
{
Name = "CUDA Device ID",
Type = LaunchOptionType.String,
Options = new() { "--device-id" }
},
new()
{
Name = "API",
Type = LaunchOptionType.Bool,
Options = new() { "--api" }
},
new()
{
Name = "Debug Logging",
Type = LaunchOptionType.Bool,
Options = new() { "--debug" }
},
LaunchOptionDefinition.Extras
};
public override string ExtraLaunchArguments => "";
public override Task<string> GetLatestVersion() => Task.FromResult("master");
public override async Task InstallPackage(string installLocation, TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null)
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing package...", isIndeterminate: true));
// Setup venv
@ -166,11 +170,13 @@ public class VladAutomatic : BaseGitPackage
{
// Run initial install
case TorchVersion.Cuda:
await venvRunner.CustomInstall("launch.py --use-cuda --debug --test", OnConsoleOutput)
await venvRunner
.CustomInstall("launch.py --use-cuda --debug --test", OnConsoleOutput)
.ConfigureAwait(false);
break;
case TorchVersion.Rocm:
await venvRunner.CustomInstall("launch.py --use-rocm --debug --test", OnConsoleOutput)
await venvRunner
.CustomInstall("launch.py --use-rocm --debug --test", OnConsoleOutput)
.ConfigureAwait(false);
break;
case TorchVersion.DirectMl:
@ -180,7 +186,8 @@ public class VladAutomatic : BaseGitPackage
break;
default:
// CPU
await venvRunner.CustomInstall("launch.py --debug --test", OnConsoleOutput)
await venvRunner
.CustomInstall("launch.py --debug --test", OnConsoleOutput)
.ConfigureAwait(false);
break;
}
@ -188,11 +195,20 @@ public class VladAutomatic : BaseGitPackage
progress?.Report(new ProgressReport(1f, isIndeterminate: false));
}
public override async Task DownloadPackage(string installLocation,
DownloadPackageVersionOptions downloadOptions, IProgress<ProgressReport>? progress = null)
public override async Task DownloadPackage(
string installLocation,
DownloadPackageVersionOptions downloadOptions,
IProgress<ProgressReport>? progress = null
)
{
progress?.Report(new ProgressReport(-1f, message: "Downloading package...",
isIndeterminate: true, type: ProgressType.Download));
progress?.Report(
new ProgressReport(
-1f,
message: "Downloading package...",
isIndeterminate: true,
type: ProgressType.Download
)
);
var installDir = new DirectoryPath(installLocation);
installDir.Create();
@ -200,22 +216,38 @@ public class VladAutomatic : BaseGitPackage
if (!string.IsNullOrWhiteSpace(downloadOptions.CommitHash))
{
await PrerequisiteHelper
.RunGit(installDir.Parent ?? "", "clone", "https://github.com/vladmandic/automatic",
installDir.Name).ConfigureAwait(false);
.RunGit(
installDir.Parent ?? "",
"clone",
"https://github.com/vladmandic/automatic",
installDir.Name
)
.ConfigureAwait(false);
await PrerequisiteHelper.RunGit(installLocation, "checkout", downloadOptions.CommitHash)
await PrerequisiteHelper
.RunGit(installLocation, "checkout", downloadOptions.CommitHash)
.ConfigureAwait(false);
}
else if (!string.IsNullOrWhiteSpace(downloadOptions.BranchName))
{
await PrerequisiteHelper
.RunGit(installDir.Parent ?? "", "clone", "-b", downloadOptions.BranchName,
"https://github.com/vladmandic/automatic", installDir.Name)
.RunGit(
installDir.Parent ?? "",
"clone",
"-b",
downloadOptions.BranchName,
"https://github.com/vladmandic/automatic",
installDir.Name
)
.ConfigureAwait(false);
}
}
public override async Task RunPackage(string installedPackagePath, string command, string arguments)
public override async Task RunPackage(
string installedPackagePath,
string command,
string arguments
)
{
await SetupVenv(installedPackagePath).ConfigureAwait(false);
@ -245,35 +277,44 @@ public class VladAutomatic : BaseGitPackage
VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, HandleExit);
}
public override async Task<InstalledPackageVersion> Update(InstalledPackage installedPackage,
TorchVersion torchVersion, IProgress<ProgressReport>? progress = null,
bool includePrerelease = false)
public override async Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false
)
{
if (installedPackage.Version is null)
{
throw new Exception("Version is null");
}
progress?.Report(new ProgressReport(-1f, message: "Downloading package update...",
isIndeterminate: true, type: ProgressType.Update));
await PrerequisiteHelper.RunGit(installedPackage.FullPath, "checkout",
installedPackage.Version.InstalledBranch).ConfigureAwait(false);
progress?.Report(
new ProgressReport(
-1f,
message: "Downloading package update...",
isIndeterminate: true,
type: ProgressType.Update
)
);
await PrerequisiteHelper
.RunGit(installedPackage.FullPath, "checkout", installedPackage.Version.InstalledBranch)
.ConfigureAwait(false);
var venvRunner = new PyVenvRunner(Path.Combine(installedPackage.FullPath!, "venv"));
venvRunner.WorkingDirectory = installedPackage.FullPath!;
venvRunner.EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables;
await venvRunner.CustomInstall("launch.py --upgrade --test", OnConsoleOutput)
await venvRunner
.CustomInstall("launch.py --upgrade --test", OnConsoleOutput)
.ConfigureAwait(false);
try
{
var output =
await PrerequisiteHelper
.GetGitOutput(installedPackage.FullPath, "rev-parse", "HEAD")
.ConfigureAwait(false);
var output = await PrerequisiteHelper
.GetGitOutput(installedPackage.FullPath, "rev-parse", "HEAD")
.ConfigureAwait(false);
return new InstalledPackageVersion
{
@ -287,9 +328,14 @@ public class VladAutomatic : BaseGitPackage
}
finally
{
progress?.Report(new ProgressReport(1f, message: "Update Complete",
isIndeterminate: false,
type: ProgressType.Update));
progress?.Report(
new ProgressReport(
1f,
message: "Update Complete",
isIndeterminate: false,
type: ProgressType.Update
)
);
}
return new InstalledPackageVersion
@ -298,7 +344,10 @@ public class VladAutomatic : BaseGitPackage
};
}
public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod)
public override Task SetupModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
)
{
switch (sharedFolderMethod)
{
@ -335,38 +384,56 @@ public class VladAutomatic : BaseGitPackage
configRoot["vae_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "VAE");
configRoot["lora_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "Lora");
configRoot["lyco_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "LyCORIS");
configRoot["embeddings_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "TextualInversion");
configRoot["hypernetwork_dir"] = Path.Combine(SettingsManager.ModelsDirectory, "Hypernetwork");
configRoot["codeformer_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "Codeformer");
configRoot["embeddings_dir"] = Path.Combine(
SettingsManager.ModelsDirectory,
"TextualInversion"
);
configRoot["hypernetwork_dir"] = Path.Combine(
SettingsManager.ModelsDirectory,
"Hypernetwork"
);
configRoot["codeformer_models_path"] = Path.Combine(
SettingsManager.ModelsDirectory,
"Codeformer"
);
configRoot["gfpgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "GFPGAN");
configRoot["bsrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "BSRGAN");
configRoot["esrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ESRGAN");
configRoot["realesrgan_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "RealESRGAN");
configRoot["realesrgan_models_path"] = Path.Combine(
SettingsManager.ModelsDirectory,
"RealESRGAN"
);
configRoot["scunet_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ScuNET");
configRoot["swinir_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "SwinIR");
configRoot["ldsr_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "LDSR");
configRoot["clip_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "CLIP");
configRoot["control_net_models_path"] = Path.Combine(SettingsManager.ModelsDirectory, "ControlNet");
var configJsonStr = JsonSerializer.Serialize(configRoot, new JsonSerializerOptions
{
WriteIndented = true
});
configRoot["control_net_models_path"] = Path.Combine(
SettingsManager.ModelsDirectory,
"ControlNet"
);
var configJsonStr = JsonSerializer.Serialize(
configRoot,
new JsonSerializerOptions { WriteIndented = true }
);
File.WriteAllText(configJsonPath, configJsonStr);
return Task.CompletedTask;
}
public override Task UpdateModelFolders(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod) =>
SetupModelFolders(installDirectory, sharedFolderMethod);
public override Task UpdateModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
) => SetupModelFolders(installDirectory, sharedFolderMethod);
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod) =>
public override Task RemoveModelFolderLinks(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
) =>
sharedFolderMethod switch
{
SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory,
sharedFolderMethod),
SharedFolderMethod.Symlink
=> base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod),
SharedFolderMethod.None => Task.CompletedTask,
_ => Task.CompletedTask
};

208
StabilityMatrix.Core/Models/Packages/VoltaML.cs

@ -19,8 +19,10 @@ public class VoltaML : BaseGitPackage
public override string Blurb => "Fast Stable Diffusion with support for AITemplate";
public override string LaunchCommand => "main.py";
public override Uri PreviewImageUri => new(
"https://github.com/LykosAI/StabilityMatrix/assets/13956642/d9a908ed-5665-41a5-a380-98458f4679a8");
public override Uri PreviewImageUri =>
new(
"https://github.com/LykosAI/StabilityMatrix/assets/13956642/d9a908ed-5665-41a5-a380-98458f4679a8"
);
// There are releases but the manager just downloads the latest commit anyways,
// so we'll just limit to commit mode to be more consistent
@ -30,112 +32,112 @@ public class VoltaML : BaseGitPackage
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper) :
base(githubApi, settingsManager, downloadService, prerequisiteHelper)
{
}
IPrerequisiteHelper prerequisiteHelper
)
: base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
// https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/main.py#L86
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders => new()
{
[SharedFolderType.StableDiffusion] = new[] {"data/models"},
[SharedFolderType.Lora] = new[] {"data/lora"},
[SharedFolderType.TextualInversion] = new[] {"data/textual-inversion"},
};
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>
new()
{
[SharedFolderType.StableDiffusion] = new[] { "data/models" },
[SharedFolderType.Lora] = new[] { "data/lora" },
[SharedFolderType.TextualInversion] = new[] { "data/textual-inversion" },
};
public override SharedFolderMethod RecommendedSharedFolderMethod =>
SharedFolderMethod.Symlink;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[] {TorchVersion.None};
public override IEnumerable<TorchVersion> AvailableTorchVersions => new[] { TorchVersion.None };
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => new[]
{
SharedFolderMethod.Symlink,
SharedFolderMethod.None
};
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };
// https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/main.py#L45
public override List<LaunchOptionDefinition> LaunchOptions => new List<LaunchOptionDefinition>
{
new()
public override List<LaunchOptionDefinition> LaunchOptions =>
new List<LaunchOptionDefinition>
{
Name = "Log Level",
Type = LaunchOptionType.Bool,
DefaultValue = "--log-level INFO",
Options =
new()
{
"--log-level DEBUG",
"--log-level INFO",
"--log-level WARNING",
"--log-level ERROR",
"--log-level CRITICAL"
}
},
new()
{
Name = "Use ngrok to expose the API",
Type = LaunchOptionType.Bool,
Options = {"--ngrok"}
},
new()
{
Name = "Expose the API to the network",
Type = LaunchOptionType.Bool,
Options = {"--host"}
},
new()
{
Name = "Skip virtualenv check",
Type = LaunchOptionType.Bool,
InitialValue = true,
Options = {"--in-container"}
},
new()
{
Name = "Force VoltaML to use a specific type of PyTorch distribution",
Type = LaunchOptionType.Bool,
Options =
Name = "Log Level",
Type = LaunchOptionType.Bool,
DefaultValue = "--log-level INFO",
Options =
{
"--log-level DEBUG",
"--log-level INFO",
"--log-level WARNING",
"--log-level ERROR",
"--log-level CRITICAL"
}
},
new()
{
"--pytorch-type cpu",
"--pytorch-type cuda",
"--pytorch-type rocm",
"--pytorch-type directml",
"--pytorch-type intel",
"--pytorch-type vulkan"
}
},
new()
{
Name = "Run in tandem with the Discord bot",
Type = LaunchOptionType.Bool,
Options = {"--bot"}
},
new()
{
Name = "Enable Cloudflare R2 bucket upload support",
Type = LaunchOptionType.Bool,
Options = {"--enable-r2"}
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "5003",
Options = {"--port"}
},
new()
{
Name = "Only install requirements and exit",
Type = LaunchOptionType.Bool,
Options = {"--install-only"}
},
LaunchOptionDefinition.Extras
};
Name = "Use ngrok to expose the API",
Type = LaunchOptionType.Bool,
Options = { "--ngrok" }
},
new()
{
Name = "Expose the API to the network",
Type = LaunchOptionType.Bool,
Options = { "--host" }
},
new()
{
Name = "Skip virtualenv check",
Type = LaunchOptionType.Bool,
InitialValue = true,
Options = { "--in-container" }
},
new()
{
Name = "Force VoltaML to use a specific type of PyTorch distribution",
Type = LaunchOptionType.Bool,
Options =
{
"--pytorch-type cpu",
"--pytorch-type cuda",
"--pytorch-type rocm",
"--pytorch-type directml",
"--pytorch-type intel",
"--pytorch-type vulkan"
}
},
new()
{
Name = "Run in tandem with the Discord bot",
Type = LaunchOptionType.Bool,
Options = { "--bot" }
},
new()
{
Name = "Enable Cloudflare R2 bucket upload support",
Type = LaunchOptionType.Bool,
Options = { "--enable-r2" }
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "5003",
Options = { "--port" }
},
new()
{
Name = "Only install requirements and exit",
Type = LaunchOptionType.Bool,
Options = { "--install-only" }
},
LaunchOptionDefinition.Extras
};
public override Task<string> GetLatestVersion() => Task.FromResult("main");
public override async Task InstallPackage(string installLocation, TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null)
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
@ -147,17 +149,23 @@ public class VoltaML : BaseGitPackage
await venvRunner.Setup(true).ConfigureAwait(false);
// Install requirements
progress?.Report(new ProgressReport(-1, "Installing Package Requirements",
isIndeterminate: true));
progress?.Report(
new ProgressReport(-1, "Installing Package Requirements", isIndeterminate: true)
);
await venvRunner
.PipInstall("rich packaging python-dotenv", OnConsoleOutput)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(1, "Installing Package Requirements",
isIndeterminate: false));
progress?.Report(
new ProgressReport(1, "Installing Package Requirements", isIndeterminate: false)
);
}
public override async Task RunPackage(string installedPackagePath, string command, string arguments)
public override async Task RunPackage(
string installedPackagePath,
string command,
string arguments
)
{
await SetupVenv(installedPackagePath).ConfigureAwait(false);

7
StabilityMatrix.Core/Services/ISettingsManager.cs

@ -37,12 +37,15 @@ public interface ISettingsManager
void RelayPropertyFor<T, TValue>(
T source,
Expression<Func<T, TValue>> sourceProperty,
Expression<Func<Settings, TValue>> settingsProperty) where T : INotifyPropertyChanged;
Expression<Func<Settings, TValue>> settingsProperty
)
where T : INotifyPropertyChanged;
/// <inheritdoc />
void RegisterPropertyChangedHandler<T>(
Expression<Func<Settings, T>> settingsProperty,
Action<T> onPropertyChanged);
Action<T> onPropertyChanged
);
/// <summary>
/// Attempts to locate and set the library path

37
StabilityMatrix.Core/Services/ModelIndexService.cs

@ -38,7 +38,8 @@ public class ModelIndexService : IModelIndexService
return await liteDbContext.LocalModelFiles
.Query()
.Where(m => m.SharedFolderType == type)
.ToArrayAsync().ConfigureAwait(false);
.ToArrayAsync()
.ConfigureAwait(false);
}
/// <inheritdoc />
@ -50,8 +51,7 @@ public class ModelIndexService : IModelIndexService
var stopwatch = Stopwatch.StartNew();
logger.LogInformation("Refreshing model index...");
using var db
= await liteDbContext.Database.BeginTransactionAsync().ConfigureAwait(false);
using var db = await liteDbContext.Database.BeginTransactionAsync().ConfigureAwait(false);
var localModelFiles = db.GetCollection<LocalModelFile>("LocalModelFiles")!;
@ -77,8 +77,10 @@ public class ModelIndexService : IModelIndexService
var relativePath = Path.GetRelativePath(modelsDir, file);
// Get shared folder name
var sharedFolderName = relativePath.Split(Path.DirectorySeparatorChar,
StringSplitOptions.RemoveEmptyEntries)[0];
var sharedFolderName = relativePath.Split(
Path.DirectorySeparatorChar,
StringSplitOptions.RemoveEmptyEntries
)[0];
// Convert to enum
var sharedFolderType = Enum.Parse<SharedFolderType>(sharedFolderName, true);
@ -90,24 +92,31 @@ public class ModelIndexService : IModelIndexService
// Try to find a connected model info
var jsonPath = file.Directory!.JoinFile(
new FilePath(file.NameWithoutExtension, ".cm-info.json"));
new FilePath(file.NameWithoutExtension, ".cm-info.json")
);
if (jsonPath.Exists)
{
var connectedModelInfo = ConnectedModelInfo.FromJson(
await jsonPath.ReadAllTextAsync().ConfigureAwait(false));
await jsonPath.ReadAllTextAsync().ConfigureAwait(false)
);
localModel.ConnectedModelInfo = connectedModelInfo;
}
// Try to find a preview image
var previewImagePath = LocalModelFile.SupportedImageExtensions
.Select(ext => file.Directory!.JoinFile($"{file.NameWithoutExtension}.preview{ext}")
).FirstOrDefault(path => path.Exists);
.Select(
ext => file.Directory!.JoinFile($"{file.NameWithoutExtension}.preview{ext}")
)
.FirstOrDefault(path => path.Exists);
if (previewImagePath != null)
{
localModel.PreviewImageRelativePath = Path.GetRelativePath(modelsDir, previewImagePath);
localModel.PreviewImageRelativePath = Path.GetRelativePath(
modelsDir,
previewImagePath
);
}
// Insert into database
@ -126,8 +135,12 @@ public class ModelIndexService : IModelIndexService
var indexDuration = indexEnd - indexStart;
var dbDuration = stopwatch.Elapsed - indexDuration;
logger.LogInformation("Model index refreshed with {Entries} entries, took {IndexDuration:F1}ms ({DbDuration:F1}ms db)",
added, indexDuration.TotalMilliseconds, dbDuration.TotalMilliseconds);
logger.LogInformation(
"Model index refreshed with {Entries} entries, took {IndexDuration:F1}ms ({DbDuration:F1}ms db)",
added,
indexDuration.TotalMilliseconds,
dbDuration.TotalMilliseconds
);
}
/// <inheritdoc />

163
StabilityMatrix.Core/Services/SettingsManager.cs

@ -20,9 +20,15 @@ public class SettingsManager : ISettingsManager
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly ReaderWriterLockSlim FileLock = new();
private static readonly string GlobalSettingsPath = Path.Combine(Compat.AppDataHome, "global.json");
private static readonly string GlobalSettingsPath = Path.Combine(
Compat.AppDataHome,
"global.json"
);
private readonly string? originalEnvPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process);
private readonly string? originalEnvPath = Environment.GetEnvironmentVariable(
"PATH",
EnvironmentVariableTarget.Process
);
// Library properties
public bool IsPortableMode { get; private set; }
@ -81,7 +87,9 @@ public class SettingsManager : ISettingsManager
{
if (!IsLibraryDirSet)
{
throw new InvalidOperationException("LibraryDir not set when BeginTransaction was called");
throw new InvalidOperationException(
"LibraryDir not set when BeginTransaction was called"
);
}
return new SettingsTransaction(this, SaveSettingsAsync);
}
@ -109,14 +117,16 @@ public class SettingsManager : ISettingsManager
if (expression.Body is not MemberExpression memberExpression)
{
throw new ArgumentException(
$"Expression must be a member expression, not {expression.Body.NodeType}");
$"Expression must be a member expression, not {expression.Body.NodeType}"
);
}
var propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException(
$"Expression member must be a property, not {memberExpression.Member.MemberType}");
$"Expression member must be a property, not {memberExpression.Member.MemberType}"
);
}
var name = propertyInfo.Name;
@ -133,7 +143,9 @@ public class SettingsManager : ISettingsManager
public void RelayPropertyFor<T, TValue>(
T source,
Expression<Func<T, TValue>> sourceProperty,
Expression<Func<Settings, TValue>> settingsProperty) where T : INotifyPropertyChanged
Expression<Func<Settings, TValue>> settingsProperty
)
where T : INotifyPropertyChanged
{
var sourceGetter = sourceProperty.Compile();
var (propertyName, assigner) = Expressions.GetAssigner(sourceProperty);
@ -148,12 +160,16 @@ public class SettingsManager : ISettingsManager
// Update source when settings change
SettingsPropertyChanged += (_, args) =>
{
if (args.PropertyName != propertyName) return;
if (args.PropertyName != propertyName)
return;
Logger.Trace(
"[RelayPropertyFor] " +
"Settings.{TargetProperty:l} -> {SourceType:l}.{SourceProperty:l}",
targetPropertyName, sourceTypeName, propertyName);
"[RelayPropertyFor] "
+ "Settings.{TargetProperty:l} -> {SourceType:l}.{SourceProperty:l}",
targetPropertyName,
sourceTypeName,
propertyName
);
sourceSetter(source, settingsGetter(Settings));
};
@ -161,12 +177,16 @@ public class SettingsManager : ISettingsManager
// Set and Save settings when source changes
source.PropertyChanged += (_, args) =>
{
if (args.PropertyName != propertyName) return;
if (args.PropertyName != propertyName)
return;
Logger.Trace(
"[RelayPropertyFor] " +
"{SourceType:l}.{SourceProperty:l} -> Settings.{TargetProperty:l}",
sourceTypeName, propertyName, targetPropertyName);
"[RelayPropertyFor] "
+ "{SourceType:l}.{SourceProperty:l} -> Settings.{TargetProperty:l}",
sourceTypeName,
propertyName,
targetPropertyName
);
settingsSetter(Settings, sourceGetter(source));
SaveSettingsAsync().SafeFireAndForget();
@ -179,7 +199,8 @@ public class SettingsManager : ISettingsManager
/// <inheritdoc />
public void RegisterPropertyChangedHandler<T>(
Expression<Func<Settings, T>> settingsProperty,
Action<T> onPropertyChanged)
Action<T> onPropertyChanged
)
{
var settingsGetter = settingsProperty.Compile();
var (propertyName, _) = Expressions.GetAssigner(settingsProperty);
@ -187,7 +208,8 @@ public class SettingsManager : ISettingsManager
// Invoke handler when settings change
SettingsPropertyChanged += (_, args) =>
{
if (args.PropertyName != propertyName) return;
if (args.PropertyName != propertyName)
return;
onPropertyChanged(settingsGetter(Settings));
};
@ -212,15 +234,18 @@ public class SettingsManager : ISettingsManager
// 2. Check %APPDATA%/StabilityMatrix/library.json
FilePath libraryJsonFile = Compat.AppDataHome + "library.json";
if (!libraryJsonFile.Exists) return false;
if (!libraryJsonFile.Exists)
return false;
try
{
var libraryJson = libraryJsonFile.ReadAllText();
var librarySettings = JsonSerializer.Deserialize<LibrarySettings>(libraryJson);
if (!string.IsNullOrWhiteSpace(librarySettings?.LibraryPath)
&& Directory.Exists(librarySettings?.LibraryPath))
if (
!string.IsNullOrWhiteSpace(librarySettings?.LibraryPath)
&& Directory.Exists(librarySettings?.LibraryPath)
)
{
LibraryDir = librarySettings.LibraryPath;
SetStaticLibraryPaths();
@ -252,7 +277,10 @@ public class SettingsManager : ISettingsManager
var libraryJsonFile = Compat.AppDataHome.JoinFile("library.json");
var library = new LibrarySettings { LibraryPath = path };
var libraryJson = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
var libraryJson = JsonSerializer.Serialize(
library,
new JsonSerializerOptions { WriteIndented = true }
);
libraryJsonFile.WriteAllText(libraryJson);
// actually create the LibraryPath directory
@ -280,17 +308,20 @@ public class SettingsManager : ISettingsManager
/// </summary>
public IEnumerable<InstalledPackage> GetOldInstalledPackages()
{
var oldSettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"StabilityMatrix", "settings.json");
var oldSettingsPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"StabilityMatrix",
"settings.json"
);
if (!File.Exists(oldSettingsPath))
yield break;
var oldSettingsJson = File.ReadAllText(oldSettingsPath);
var oldSettings = JsonSerializer.Deserialize<Settings>(oldSettingsJson, new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
});
var oldSettings = JsonSerializer.Deserialize<Settings>(
oldSettingsJson,
new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } }
);
// Absolute paths are old formats requiring migration
#pragma warning disable CS0618
@ -308,17 +339,20 @@ public class SettingsManager : ISettingsManager
public Guid GetOldActivePackageId()
{
var oldSettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"StabilityMatrix", "settings.json");
var oldSettingsPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"StabilityMatrix",
"settings.json"
);
if (!File.Exists(oldSettingsPath))
return default;
var oldSettingsJson = File.ReadAllText(oldSettingsPath);
var oldSettings = JsonSerializer.Deserialize<Settings>(oldSettingsJson, new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
});
var oldSettings = JsonSerializer.Deserialize<Settings>(
oldSettingsJson,
new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } }
);
if (oldSettings == null)
return default;
@ -343,7 +377,8 @@ public class SettingsManager : ISettingsManager
/// </summary>
public void InsertPathExtensions()
{
if (Settings.PathExtensions == null) return;
if (Settings.PathExtensions == null)
return;
var toInsert = GetPathExtensionsAsString();
// Append the original path, if any
if (originalEnvPath != null)
@ -367,7 +402,9 @@ public class SettingsManager : ISettingsManager
public void SetLastUpdateCheck(InstalledPackage package)
{
var installedPackage = Settings.InstalledPackages.First(p => p.DisplayName == package.DisplayName);
var installedPackage = Settings.InstalledPackages.First(
p => p.DisplayName == package.DisplayName
);
installedPackage.LastUpdateCheck = package.LastUpdateCheck;
installedPackage.UpdateAvailable = package.UpdateAvailable;
SaveSettings();
@ -395,9 +432,14 @@ public class SettingsManager : ISettingsManager
public string? GetActivePackageHost()
{
var package = Settings.InstalledPackages.FirstOrDefault(x => x.Id == Settings.ActiveInstalledPackageId);
if (package == null) return null;
var hostOption = package.LaunchArgs?.FirstOrDefault(x => x.Name.ToLowerInvariant() == "host");
var package = Settings.InstalledPackages.FirstOrDefault(
x => x.Id == Settings.ActiveInstalledPackageId
);
if (package == null)
return null;
var hostOption = package.LaunchArgs?.FirstOrDefault(
x => x.Name.ToLowerInvariant() == "host"
);
if (hostOption?.OptionValue != null)
{
return hostOption.OptionValue as string;
@ -407,9 +449,14 @@ public class SettingsManager : ISettingsManager
public string? GetActivePackagePort()
{
var package = Settings.InstalledPackages.FirstOrDefault(x => x.Id == Settings.ActiveInstalledPackageId);
if (package == null) return null;
var portOption = package.LaunchArgs?.FirstOrDefault(x => x.Name.ToLowerInvariant() == "port");
var package = Settings.InstalledPackages.FirstOrDefault(
x => x.Id == Settings.ActiveInstalledPackageId
);
if (package == null)
return null;
var portOption = package.LaunchArgs?.FirstOrDefault(
x => x.Name.ToLowerInvariant() == "port"
);
if (portOption?.OptionValue != null)
{
return portOption.OptionValue as string;
@ -434,7 +481,8 @@ public class SettingsManager : ISettingsManager
public bool IsSharedFolderCategoryVisible(SharedFolderType type)
{
// False for default
if (type == 0) return false;
if (type == 0)
return false;
return Settings.SharedFolderVisibleCategories?.HasFlag(type) ?? false;
}
@ -455,7 +503,7 @@ public class SettingsManager : ISettingsManager
public void SetEulaAccepted()
{
var globalSettings = new GlobalSettings {EulaAccepted = true};
var globalSettings = new GlobalSettings { EulaAccepted = true };
var json = JsonSerializer.Serialize(globalSettings);
File.WriteAllText(GlobalSettingsPath, json);
}
@ -472,10 +520,14 @@ public class SettingsManager : ISettingsManager
var modelHashes = new HashSet<string>();
var sharedModelDirectory = Path.Combine(LibraryDir, "Models");
if (!Directory.Exists(sharedModelDirectory)) return;
if (!Directory.Exists(sharedModelDirectory))
return;
var connectedModelJsons = Directory.GetFiles(sharedModelDirectory, "*.cm-info.json",
SearchOption.AllDirectories);
var connectedModelJsons = Directory.GetFiles(
sharedModelDirectory,
"*.cm-info.json",
SearchOption.AllDirectories
);
foreach (var jsonFile in connectedModelJsons)
{
var json = File.ReadAllText(jsonFile);
@ -515,9 +567,10 @@ public class SettingsManager : ISettingsManager
var modifiedDefaultSerializerOptions =
SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions();
modifiedDefaultSerializerOptions.Converters.Add(new JsonStringEnumConverter());
Settings =
JsonSerializer.Deserialize<Settings>(settingsContent,
modifiedDefaultSerializerOptions)!;
Settings = JsonSerializer.Deserialize<Settings>(
settingsContent,
modifiedDefaultSerializerOptions
)!;
}
finally
{
@ -535,11 +588,14 @@ public class SettingsManager : ISettingsManager
File.Create(SettingsPath).Close();
}
var json = JsonSerializer.Serialize(Settings, new JsonSerializerOptions
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter() }
});
var json = JsonSerializer.Serialize(
Settings,
new JsonSerializerOptions
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter() }
}
);
File.WriteAllText(SettingsPath, json);
}
finally
@ -553,4 +609,3 @@ public class SettingsManager : ISettingsManager
return Task.Run(SaveSettings);
}
}

34
StabilityMatrix.Core/Services/TrackedDownloadService.cs

@ -15,7 +15,10 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable
private readonly IDownloadService downloadService;
private readonly ISettingsManager settingsManager;
private readonly ConcurrentDictionary<Guid, (TrackedDownload Download, FileStream Stream)> downloads = new();
private readonly ConcurrentDictionary<
Guid,
(TrackedDownload Download, FileStream Stream)
> downloads = new();
public IEnumerable<TrackedDownload> Downloads => downloads.Values.Select(x => x.Download);
@ -25,7 +28,8 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable
public TrackedDownloadService(
ILogger<TrackedDownloadService> logger,
IDownloadService downloadService,
ISettingsManager settingsManager)
ISettingsManager settingsManager
)
{
this.logger = logger;
this.downloadService = downloadService;
@ -36,7 +40,8 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable
{
var downloadsDir = new DirectoryPath(settingsManager.DownloadsDirectory);
// Ignore if not exist
if (!downloadsDir.Exists) return;
if (!downloadsDir.Exists)
return;
LoadInProgressDownloads(downloadsDir);
});
@ -60,7 +65,11 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable
var downloadsDir = new DirectoryPath(settingsManager.DownloadsDirectory);
downloadsDir.Create();
var jsonFile = downloadsDir.JoinFile($"{download.Id}.json");
var jsonFileStream = jsonFile.Info.Open(FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read);
var jsonFileStream = jsonFile.Info.Open(
FileMode.CreateNew,
FileAccess.ReadWrite,
FileShare.Read
);
// Serialize to json
var json = JsonSerializer.Serialize(download);
@ -118,7 +127,9 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable
{
downloadInfo.Item2.Dispose();
// Delete json file
new DirectoryPath(settingsManager.DownloadsDirectory).JoinFile($"{download.Id}.json").Delete();
new DirectoryPath(settingsManager.DownloadsDirectory)
.JoinFile($"{download.Id}.json")
.Delete();
logger.LogDebug("Removed download {Download}", download.FileName);
}
}
@ -159,11 +170,18 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable
else if (download.ProgressState != ProgressState.Inactive)
{
// If the download is not inactive, skip it
logger.LogWarning("Skipping download {Download} with state {State}", download.FileName, download.ProgressState);
logger.LogWarning(
"Skipping download {Download} with state {State}",
download.FileName,
download.ProgressState
);
fileStream.Dispose();
// Delete json file
logger.LogDebug("Deleting json file for {Download} with unsupported state", download.FileName);
logger.LogDebug(
"Deleting json file for {Download} with unsupported state",
download.FileName
);
file.Delete();
continue;
}
@ -213,7 +231,7 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable
for (var i = 0; i < 10; i++)
{
if (tempFile is {Exists: false})
if (tempFile is { Exists: false })
{
return tempFile.Name;
}

Loading…
Cancel
Save