Browse Source

New installer flow with tabs n extensions n stuff

pull/438/head
JT 11 months ago
parent
commit
177d9cb024
  1. 2
      StabilityMatrix.Avalonia/App.axaml.cs
  2. 110
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  3. 4
      StabilityMatrix.Avalonia/Services/NavigationService.cs
  4. 8
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  5. 66
      StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs
  6. 88
      StabilityMatrix.Avalonia/ViewModels/Dialogs/NewInstallerDialogViewModel.cs
  7. 13
      StabilityMatrix.Avalonia/ViewModels/ExtensionViewModel.cs
  8. 66
      StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs
  9. 276
      StabilityMatrix.Avalonia/ViewModels/PackageInstallDetailViewModel.cs
  10. 37
      StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
  11. 2
      StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml.cs
  12. 233
      StabilityMatrix.Avalonia/Views/Dialogs/NewInstallerDialog.axaml
  13. 13
      StabilityMatrix.Avalonia/Views/Dialogs/NewInstallerDialog.axaml.cs
  14. 49
      StabilityMatrix.Avalonia/Views/NewPackageManagerPage.axaml
  15. 99
      StabilityMatrix.Avalonia/Views/NewPackageManagerPage.axaml.cs
  16. 195
      StabilityMatrix.Avalonia/Views/PackageInstallDetailView.axaml
  17. 13
      StabilityMatrix.Avalonia/Views/PackageInstallDetailView.axaml.cs
  18. 6
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml
  19. 1
      StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs
  20. 3
      StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
  21. 15
      StabilityMatrix.Core/Models/PackageModification/InstallExtensionStep.cs
  22. 7
      StabilityMatrix.Core/Models/PackageType.cs
  23. 7
      StabilityMatrix.Core/Models/Packages/BasePackage.cs
  24. 4
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  25. 28
      StabilityMatrix.Core/Models/Packages/Extensions/ComfyManager.cs
  26. 24
      StabilityMatrix.Core/Models/Packages/Extensions/ExtensionBase.cs
  27. 4
      StabilityMatrix.Core/Models/Packages/KohyaSs.cs
  28. 2
      StabilityMatrix.Core/Models/Packages/OneTrainer.cs

2
StabilityMatrix.Avalonia/App.axaml.cs

@ -271,7 +271,7 @@ public sealed class App : Application
{ {
provider.GetRequiredService<LaunchPageViewModel>(), provider.GetRequiredService<LaunchPageViewModel>(),
provider.GetRequiredService<InferenceViewModel>(), provider.GetRequiredService<InferenceViewModel>(),
provider.GetRequiredService<PackageManagerViewModel>(), provider.GetRequiredService<NewPackageManagerViewModel>(),
provider.GetRequiredService<CheckpointsPageViewModel>(), provider.GetRequiredService<CheckpointsPageViewModel>(),
provider.GetRequiredService<CheckpointBrowserViewModel>(), provider.GetRequiredService<CheckpointBrowserViewModel>(),
provider.GetRequiredService<OutputsPageViewModel>() provider.GetRequiredService<OutputsPageViewModel>()

110
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -12,6 +12,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute; using NSubstitute;
using NSubstitute.ReturnsExtensions; using NSubstitute.ReturnsExtensions;
using Semver; using Semver;
@ -184,6 +185,24 @@ public static class DesignData
InstallerViewModel.SelectedPackage = InstallerViewModel.AvailablePackages[0]; InstallerViewModel.SelectedPackage = InstallerViewModel.AvailablePackages[0];
InstallerViewModel.ReleaseNotes = "## Release Notes\nThis is a test release note."; InstallerViewModel.ReleaseNotes = "## Release Notes\nThis is a test release note.";
NewInstallerDialogViewModel = Services.GetRequiredService<NewInstallerDialogViewModel>();
NewInstallerDialogViewModel.InferencePackages = new ObservableCollection<BasePackage>(
packageFactory.GetPackagesByType(PackageType.SdInference).OrderBy(p => p.InstallerSortOrder)
);
NewInstallerDialogViewModel.TrainingPackages = new ObservableCollection<BasePackage>(
packageFactory.GetPackagesByType(PackageType.SdTraining).OrderBy(p => p.InstallerSortOrder)
);
PackageInstallDetailViewModel = new PackageInstallDetailViewModel(
packageFactory.GetAllAvailablePackages().FirstOrDefault() as BaseGitPackage,
settingsManager,
notificationService,
null,
null,
null,
null
);
ObservableCacheEx.AddOrUpdate( ObservableCacheEx.AddOrUpdate(
CheckpointsPageViewModel.CheckpointFoldersCache, CheckpointsPageViewModel.CheckpointFoldersCache,
new CheckpointFolder[] new CheckpointFolder[]
@ -375,25 +394,27 @@ public static class DesignData
new() { FilePath = "~/Models/Lora/model.safetensors", Title = "Some model" } new() { FilePath = "~/Models/Lora/model.safetensors", Title = "Some model" }
}; };
ProgressManagerViewModel ProgressManagerViewModel.ProgressItems.AddRange(
.ProgressItems new ProgressItemViewModelBase[]
.AddRange( {
new ProgressItemViewModelBase[] new ProgressItemViewModel(
{ new ProgressItem(
new ProgressItemViewModel( Guid.NewGuid(),
new ProgressItem(Guid.NewGuid(), "Test File.exe", new ProgressReport(0.5f, "Downloading...")) "Test File.exe",
), new ProgressReport(0.5f, "Downloading...")
new MockDownloadProgressItemViewModel(
"Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe"
),
new PackageInstallProgressItemViewModel(
new PackageModificationRunner
{
CurrentProgress = new ProgressReport(0.5f, "Installing package...")
}
) )
} ),
); new MockDownloadProgressItemViewModel(
"Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe"
),
new PackageInstallProgressItemViewModel(
new PackageModificationRunner
{
CurrentProgress = new ProgressReport(0.5f, "Installing package...")
}
)
}
);
UpdateViewModel = Services.GetRequiredService<UpdateViewModel>(); UpdateViewModel = Services.GetRequiredService<UpdateViewModel>();
UpdateViewModel.CurrentVersionText = "v2.0.0"; UpdateViewModel.CurrentVersionText = "v2.0.0";
@ -406,6 +427,12 @@ public static class DesignData
[NotNull] [NotNull]
public static InstallerViewModel? InstallerViewModel { get; private set; } public static InstallerViewModel? InstallerViewModel { get; private set; }
[NotNull]
public static NewInstallerDialogViewModel? NewInstallerDialogViewModel { get; private set; }
[NotNull]
public static PackageInstallDetailViewModel? PackageInstallDetailViewModel { get; private set; }
[NotNull] [NotNull]
public static LaunchOptionsViewModel? LaunchOptionsViewModel { get; private set; } public static LaunchOptionsViewModel? LaunchOptionsViewModel { get; private set; }
@ -415,12 +442,14 @@ public static class DesignData
public static ServiceManager<ViewModelBase> DialogFactory => public static ServiceManager<ViewModelBase> DialogFactory =>
Services.GetRequiredService<ServiceManager<ViewModelBase>>(); Services.GetRequiredService<ServiceManager<ViewModelBase>>();
public static MainWindowViewModel MainWindowViewModel => Services.GetRequiredService<MainWindowViewModel>(); public static MainWindowViewModel MainWindowViewModel =>
Services.GetRequiredService<MainWindowViewModel>();
public static FirstLaunchSetupViewModel FirstLaunchSetupViewModel => public static FirstLaunchSetupViewModel FirstLaunchSetupViewModel =>
Services.GetRequiredService<FirstLaunchSetupViewModel>(); Services.GetRequiredService<FirstLaunchSetupViewModel>();
public static LaunchPageViewModel LaunchPageViewModel => Services.GetRequiredService<LaunchPageViewModel>(); public static LaunchPageViewModel LaunchPageViewModel =>
Services.GetRequiredService<LaunchPageViewModel>();
public static HuggingFacePageViewModel HuggingFacePageViewModel => public static HuggingFacePageViewModel HuggingFacePageViewModel =>
Services.GetRequiredService<HuggingFacePageViewModel>(); Services.GetRequiredService<HuggingFacePageViewModel>();
@ -454,7 +483,10 @@ public static class DesignData
vm.SetPackages(settings.Settings.InstalledPackages); vm.SetPackages(settings.Settings.InstalledPackages);
vm.SetUnknownPackages( vm.SetUnknownPackages(
new InstalledPackage[] { UnknownInstalledPackage.FromDirectoryName("sd-unknown-with-long-name"), } new InstalledPackage[]
{
UnknownInstalledPackage.FromDirectoryName("sd-unknown-with-long-name"),
}
); );
vm.PackageCards[0].IsUpdateAvailable = true; vm.PackageCards[0].IsUpdateAvailable = true;
@ -471,10 +503,14 @@ public static class DesignData
public static SettingsViewModel SettingsViewModel => Services.GetRequiredService<SettingsViewModel>(); public static SettingsViewModel SettingsViewModel => Services.GetRequiredService<SettingsViewModel>();
public static NewPackageManagerViewModel NewPackageManagerViewModel =>
Services.GetRequiredService<NewPackageManagerViewModel>();
public static InferenceSettingsViewModel InferenceSettingsViewModel => public static InferenceSettingsViewModel InferenceSettingsViewModel =>
Services.GetRequiredService<InferenceSettingsViewModel>(); Services.GetRequiredService<InferenceSettingsViewModel>();
public static MainSettingsViewModel MainSettingsViewModel => Services.GetRequiredService<MainSettingsViewModel>(); public static MainSettingsViewModel MainSettingsViewModel =>
Services.GetRequiredService<MainSettingsViewModel>();
public static AccountSettingsViewModel AccountSettingsViewModel => public static AccountSettingsViewModel AccountSettingsViewModel =>
Services.GetRequiredService<AccountSettingsViewModel>(); Services.GetRequiredService<AccountSettingsViewModel>();
@ -558,7 +594,10 @@ The gallery images are often inpainted, but you will get something very similar
} }
} }
}; };
var sampleViewModel = new ModelVersionViewModel(new HashSet<string> { "ABCD" }, sampleCivitVersions[0]); var sampleViewModel = new ModelVersionViewModel(
new HashSet<string> { "ABCD" },
sampleCivitVersions[0]
);
// Sample data for dialogs // Sample data for dialogs
vm.Versions = new List<ModelVersionViewModel> { sampleViewModel }; vm.Versions = new List<ModelVersionViewModel> { sampleViewModel };
@ -646,7 +685,8 @@ The gallery images are often inpainted, but you will get something very similar
public static InferenceImageUpscaleViewModel InferenceImageUpscaleViewModel => public static InferenceImageUpscaleViewModel InferenceImageUpscaleViewModel =>
DialogFactory.Get<InferenceImageUpscaleViewModel>(); DialogFactory.Get<InferenceImageUpscaleViewModel>();
public static PackageImportViewModel PackageImportViewModel => DialogFactory.Get<PackageImportViewModel>(); public static PackageImportViewModel PackageImportViewModel =>
DialogFactory.Get<PackageImportViewModel>();
public static RefreshBadgeViewModel RefreshBadgeViewModel => new() { State = ProgressState.Success }; public static RefreshBadgeViewModel RefreshBadgeViewModel => new() { State = ProgressState.Success };
@ -721,7 +761,8 @@ The gallery images are often inpainted, but you will get something very similar
); );
}); });
public static ImageFolderCardViewModel ImageFolderCardViewModel => DialogFactory.Get<ImageFolderCardViewModel>(); public static ImageFolderCardViewModel ImageFolderCardViewModel =>
DialogFactory.Get<ImageFolderCardViewModel>();
public static FreeUCardViewModel FreeUCardViewModel => DialogFactory.Get<FreeUCardViewModel>(); public static FreeUCardViewModel FreeUCardViewModel => DialogFactory.Get<FreeUCardViewModel>();
@ -774,7 +815,8 @@ The gallery images are often inpainted, but you will get something very similar
public static UpscalerCardViewModel UpscalerCardViewModel => DialogFactory.Get<UpscalerCardViewModel>(); public static UpscalerCardViewModel UpscalerCardViewModel => DialogFactory.Get<UpscalerCardViewModel>();
public static BatchSizeCardViewModel BatchSizeCardViewModel => DialogFactory.Get<BatchSizeCardViewModel>(); public static BatchSizeCardViewModel BatchSizeCardViewModel =>
DialogFactory.Get<BatchSizeCardViewModel>();
public static BatchSizeCardViewModel BatchSizeCardViewModelWithIndexOption => public static BatchSizeCardViewModel BatchSizeCardViewModelWithIndexOption =>
DialogFactory.Get<BatchSizeCardViewModel>(vm => DialogFactory.Get<BatchSizeCardViewModel>(vm =>
@ -806,7 +848,6 @@ The gallery images are often inpainted, but you will get something very similar
} }
public static IEnumerable<HybridModelFile> SampleHybridModels { get; } = public static IEnumerable<HybridModelFile> SampleHybridModels { get; } =
[ [
HybridModelFile.FromLocal( HybridModelFile.FromLocal(
new LocalModelFile new LocalModelFile
@ -868,7 +909,8 @@ The gallery images are often inpainted, but you will get something very similar
public static InferenceConnectionHelpViewModel InferenceConnectionHelpViewModel => public static InferenceConnectionHelpViewModel InferenceConnectionHelpViewModel =>
DialogFactory.Get<InferenceConnectionHelpViewModel>(); DialogFactory.Get<InferenceConnectionHelpViewModel>();
public static SelectImageCardViewModel SelectImageCardViewModel => DialogFactory.Get<SelectImageCardViewModel>(); public static SelectImageCardViewModel SelectImageCardViewModel =>
DialogFactory.Get<SelectImageCardViewModel>();
public static SelectImageCardViewModel SelectImageCardViewModel_WithImage => public static SelectImageCardViewModel SelectImageCardViewModel_WithImage =>
DialogFactory.Get<SelectImageCardViewModel>(vm => DialogFactory.Get<SelectImageCardViewModel>(vm =>
@ -881,12 +923,17 @@ The gallery images are often inpainted, but you will get something very similar
}); });
public static ImageSource SampleImageSource => public static ImageSource SampleImageSource =>
new(new Uri("https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500")) new(
new Uri(
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500"
)
)
{ {
Label = "Test Image" Label = "Test Image"
}; };
public static ControlNetCardViewModel ControlNetCardViewModel => DialogFactory.Get<ControlNetCardViewModel>(); public static ControlNetCardViewModel ControlNetCardViewModel =>
DialogFactory.Get<ControlNetCardViewModel>();
public static Indexer Types { get; } = new(); public static Indexer Types { get; } = new();
@ -896,7 +943,8 @@ The gallery images are often inpainted, but you will get something very similar
{ {
get get
{ {
var type = Type.GetType(typeName) ?? throw new ArgumentException($"Type {typeName} not found"); var type =
Type.GetType(typeName) ?? throw new ArgumentException($"Type {typeName} not found");
try try
{ {
return Services.GetService(type); return Services.GetService(type);

4
StabilityMatrix.Avalonia/Services/NavigationService.cs

@ -22,6 +22,10 @@ namespace StabilityMatrix.Avalonia.Services;
ImplType = typeof(NavigationService<SettingsViewModel>), ImplType = typeof(NavigationService<SettingsViewModel>),
InterfaceType = typeof(INavigationService<SettingsViewModel>) InterfaceType = typeof(INavigationService<SettingsViewModel>)
)] )]
[Singleton(
ImplType = typeof(NavigationService<NewPackageManagerViewModel>),
InterfaceType = typeof(INavigationService<NewPackageManagerViewModel>)
)]
public class NavigationService<T> : INavigationService<T> public class NavigationService<T> : INavigationService<T>
{ {
private Frame? _frame; private Frame? _frame;

8
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -173,6 +173,14 @@
<DependentUpon>VideoOutputSettingsCard.axaml</DependentUpon> <DependentUpon>VideoOutputSettingsCard.axaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Update="Views\NewPackageManagerPage.axaml.cs">
<DependentUpon>NewPackageManagerPage.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Views\PackageInstallDetailView.axaml.cs">
<DependentUpon>PackageInstallDetailView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup> </ItemGroup>
<!-- set HUSKY to 0 to disable, or opt-in during CI by setting HUSKY to 1 --> <!-- set HUSKY to 0 to disable, or opt-in during CI by setting HUSKY to 1 -->

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

@ -20,6 +20,7 @@ using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory; using StabilityMatrix.Core.Helper.Factory;
@ -34,9 +35,9 @@ using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[ManagedService] [View(typeof(InstallerDialog))]
[Transient] [Transient, ManagedService]
public partial class InstallerViewModel : ContentDialogViewModelBase public partial class InstallerViewModel : PageViewModelBase
{ {
private readonly ISettingsManager settingsManager; private readonly ISettingsManager settingsManager;
private readonly IPackageFactory packageFactory; private readonly IPackageFactory packageFactory;
@ -46,6 +47,8 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
private readonly IPrerequisiteHelper prerequisiteHelper; private readonly IPrerequisiteHelper prerequisiteHelper;
private readonly ILogger<InstallerViewModel> logger; private readonly ILogger<InstallerViewModel> logger;
public override string Title => "Add Package";
[ObservableProperty] [ObservableProperty]
private BasePackage selectedPackage; private BasePackage selectedPackage;
@ -90,11 +93,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
// Version types (release or commit) // Version types (release or commit)
[ObservableProperty] [ObservableProperty]
[NotifyPropertyChangedFor( [NotifyPropertyChangedFor(nameof(ReleaseLabelText), nameof(IsReleaseMode), nameof(SelectedVersion))]
nameof(ReleaseLabelText),
nameof(IsReleaseMode),
nameof(SelectedVersion)
)]
private PackageVersionType selectedVersionType = PackageVersionType.Commit; private PackageVersionType selectedVersionType = PackageVersionType.Commit;
[ObservableProperty] [ObservableProperty]
@ -106,22 +105,16 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
[NotifyPropertyChangedFor(nameof(CanInstall))] [NotifyPropertyChangedFor(nameof(CanInstall))]
private bool isLoading; private bool isLoading;
public string ReleaseLabelText => public string ReleaseLabelText => IsReleaseMode ? Resources.Label_Version : Resources.Label_Branch;
IsReleaseMode ? Resources.Label_Version : Resources.Label_Branch;
public bool IsReleaseMode public bool IsReleaseMode
{ {
get => SelectedVersionType == PackageVersionType.GithubRelease; get => SelectedVersionType == PackageVersionType.GithubRelease;
set => set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit;
SelectedVersionType = value
? PackageVersionType.GithubRelease
: PackageVersionType.Commit;
} }
public bool IsReleaseModeAvailable => public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
public bool ShowTorchVersionOptions => SelectedTorchVersion != TorchVersion.None; public bool ShowTorchVersionOptions => SelectedTorchVersion != TorchVersion.None;
public bool CanInstall => public bool CanInstall => !string.IsNullOrWhiteSpace(InstallName) && !ShowDuplicateWarning && !IsLoading;
!string.IsNullOrWhiteSpace(InstallName) && !ShowDuplicateWarning && !IsLoading;
public IEnumerable<IPackageStep> Steps { get; set; } public IEnumerable<IPackageStep> Steps { get; set; }
@ -148,6 +141,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
AvailablePackages = new ObservableCollection<BasePackage>( AvailablePackages = new ObservableCollection<BasePackage>(
filtered.Any() ? filtered : packageFactory.GetAllAvailablePackages() filtered.Any() ? filtered : packageFactory.GetAllAvailablePackages()
); );
SelectedPackage = AvailablePackages.FirstOrDefault();
ShowIncompatiblePackages = !filtered.Any(); ShowIncompatiblePackages = !filtered.Any();
} }
@ -207,7 +201,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
); );
if (result.IsSuccessful) if (result.IsSuccessful)
{ {
OnPrimaryButtonClick(); // OnPrimaryButtonClick();
} }
else else
{ {
@ -254,10 +248,8 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
if (IsReleaseMode) if (IsReleaseMode)
{ {
downloadOptions.VersionTag = downloadOptions.VersionTag =
SelectedVersion?.TagName SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null");
?? throw new NullReferenceException("Selected version is null"); downloadOptions.IsLatest = AvailableVersions?.First().TagName == downloadOptions.VersionTag;
downloadOptions.IsLatest =
AvailableVersions?.First().TagName == downloadOptions.VersionTag;
downloadOptions.IsPrerelease = SelectedVersion.IsPrerelease; downloadOptions.IsPrerelease = SelectedVersion.IsPrerelease;
installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag; installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag;
@ -268,21 +260,15 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
downloadOptions.CommitHash = downloadOptions.CommitHash =
SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null"); SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null");
downloadOptions.BranchName = downloadOptions.BranchName =
SelectedVersion?.TagName SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null");
?? throw new NullReferenceException("Selected version is null");
downloadOptions.IsLatest = AvailableCommits?.First().Sha == SelectedCommit.Sha; downloadOptions.IsLatest = AvailableCommits?.First().Sha == SelectedCommit.Sha;
installedVersion.InstalledBranch = installedVersion.InstalledBranch =
SelectedVersion?.TagName SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null");
?? throw new NullReferenceException("Selected version is null");
installedVersion.InstalledCommitSha = downloadOptions.CommitHash; installedVersion.InstalledCommitSha = downloadOptions.CommitHash;
} }
var downloadStep = new DownloadPackageVersionStep( var downloadStep = new DownloadPackageVersionStep(SelectedPackage, installLocation, downloadOptions);
SelectedPackage,
installLocation,
downloadOptions
);
var installStep = new InstallPackageStep( var installStep = new InstallPackageStep(
SelectedPackage, SelectedPackage,
SelectedTorchVersion, SelectedTorchVersion,
@ -326,7 +312,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
public void Cancel() public void Cancel()
{ {
OnCloseButtonClick(); // OnCloseButtonClick();
} }
partial void OnShowIncompatiblePackagesChanged(bool value) partial void OnShowIncompatiblePackagesChanged(bool value)
@ -351,9 +337,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
else else
{ {
// First try to find the package-defined main branch // First try to find the package-defined main branch
var version = AvailableVersions.FirstOrDefault( var version = AvailableVersions.FirstOrDefault(x => x.TagName == SelectedPackage.MainBranch);
x => x.TagName == SelectedPackage.MainBranch
);
// If not found, try main // If not found, try main
version ??= AvailableVersions.FirstOrDefault(x => x.TagName == "main"); version ??= AvailableVersions.FirstOrDefault(x => x.TagName == "main");
@ -406,8 +390,8 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
if (SelectedPackage is null || Design.IsDesignMode) if (SelectedPackage is null || Design.IsDesignMode)
return; return;
Dispatcher.UIThread Dispatcher
.InvokeAsync(async () => .UIThread.InvokeAsync(async () =>
{ {
logger.LogDebug($"Release mode: {IsReleaseMode}"); logger.LogDebug($"Release mode: {IsReleaseMode}");
var versionOptions = await SelectedPackage.GetAllVersionOptions(); var versionOptions = await SelectedPackage.GetAllVersionOptions();
@ -425,9 +409,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
if (!IsReleaseMode) if (!IsReleaseMode)
{ {
var commits = ( var commits = (await SelectedPackage.GetAllCommits(SelectedVersion.TagName))?.ToList();
await SelectedPackage.GetAllCommits(SelectedVersion.TagName)
)?.ToList();
if (commits is null || commits.Count == 0) if (commits is null || commits.Count == 0)
return; return;
@ -506,4 +488,6 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
.SafeFireAndForget(); .SafeFireAndForget();
} }
} }
public override IconSource IconSource { get; }
} }

88
StabilityMatrix.Avalonia/ViewModels/Dialogs/NewInstallerDialogViewModel.cs

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(NewInstallerDialog))]
[Transient, ManagedService]
public partial class NewInstallerDialogViewModel : PageViewModelBase
{
private readonly INavigationService<NewPackageManagerViewModel> packageNavigationService;
private readonly ISettingsManager settingsManager;
private readonly INotificationService notificationService;
private readonly ILogger<PackageInstallDetailViewModel> logger;
private readonly IPyRunner pyRunner;
private readonly IPrerequisiteHelper prerequisiteHelper;
[ObservableProperty]
private IEnumerable<BasePackage> inferencePackages;
[ObservableProperty]
private IEnumerable<BasePackage> trainingPackages;
public NewInstallerDialogViewModel(
IPackageFactory packageFactory,
INavigationService<NewPackageManagerViewModel> packageNavigationService,
ISettingsManager settingsManager,
INotificationService notificationService,
ILogger<PackageInstallDetailViewModel> logger,
IPyRunner pyRunner,
IPrerequisiteHelper prerequisiteHelper
)
{
this.packageNavigationService = packageNavigationService;
this.settingsManager = settingsManager;
this.notificationService = notificationService;
this.logger = logger;
this.pyRunner = pyRunner;
this.prerequisiteHelper = prerequisiteHelper;
inferencePackages = packageFactory
.GetPackagesByType(PackageType.SdInference)
.OrderBy(p => p.InstallerSortOrder);
trainingPackages = packageFactory
.GetPackagesByType(PackageType.SdTraining)
.OrderBy(p => p.InstallerSortOrder);
}
public override string Title => "Add Package";
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Add };
public void OnPackageSelected(BaseGitPackage package)
{
if (package is null)
{
return;
}
var vm = new PackageInstallDetailViewModel(
package,
settingsManager,
notificationService,
logger,
pyRunner,
prerequisiteHelper,
packageNavigationService
);
Dispatcher.UIThread.Post(
() => packageNavigationService.NavigateTo(vm, BetterSlideNavigationTransition.PageSlideFromRight),
DispatcherPriority.Send
);
}
}

13
StabilityMatrix.Avalonia/ViewModels/ExtensionViewModel.cs

@ -0,0 +1,13 @@
using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Models.Packages.Extensions;
namespace StabilityMatrix.Avalonia.ViewModels;
public partial class ExtensionViewModel() : ViewModelBase
{
[ObservableProperty]
private bool isSelected;
public ExtensionBase Extension { get; init; }
}

66
StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs

@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(NewPackageManagerPage))]
[Singleton]
public partial class NewPackageManagerViewModel : PageViewModelBase
{
public override string Title => "Packages";
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
public IReadOnlyList<PageViewModelBase> SubPages { get; }
[ObservableProperty]
private ObservableCollection<PageViewModelBase> currentPagePath = [];
[ObservableProperty]
private PageViewModelBase? currentPage;
public NewPackageManagerViewModel(ServiceManager<ViewModelBase> vmFactory)
{
SubPages = new PageViewModelBase[]
{
vmFactory.Get<PackageManagerViewModel>(),
vmFactory.Get<NewInstallerDialogViewModel>(),
};
CurrentPagePath.AddRange(SubPages);
CurrentPage = SubPages[0];
}
partial void OnCurrentPageChanged(PageViewModelBase? value)
{
if (value is null)
{
return;
}
if (value is PackageManagerViewModel)
{
CurrentPagePath.Clear();
CurrentPagePath.Add(value);
}
else if (value is PackageInstallDetailViewModel)
{
CurrentPagePath.Add(value);
}
else
{
CurrentPagePath.Clear();
CurrentPagePath.AddRange(new[] { SubPages[0], value });
}
}
}

276
StabilityMatrix.Avalonia/ViewModels/PackageInstallDetailViewModel.cs

@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(PackageInstallDetailView))]
public partial class PackageInstallDetailViewModel(
BaseGitPackage package,
ISettingsManager settingsManager,
INotificationService notificationService,
ILogger<PackageInstallDetailViewModel> logger,
IPyRunner pyRunner,
IPrerequisiteHelper prerequisiteHelper,
INavigationService<NewPackageManagerViewModel> packageNavigationService
) : PageViewModelBase
{
public BaseGitPackage SelectedPackage { get; } = package;
public override string Title { get; } = package.DisplayName;
public override IconSource IconSource => new SymbolIconSource();
public string FullInstallPath => Path.Combine(settingsManager.LibraryDir, "Packages", InstallName);
public bool ShowReleaseMode => SelectedPackage.ShouldIgnoreReleases == false;
public string ReleaseLabelText => IsReleaseMode ? Resources.Label_Version : Resources.Label_Branch;
public bool ShowTorchVersionOptions => SelectedTorchVersion != TorchVersion.None;
public bool ShowExtensions => SelectedPackage.AvailableExtensions.Any();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullInstallPath))]
private string installName = package.DisplayName;
[ObservableProperty]
private bool showDuplicateWarning;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ReleaseLabelText))]
private bool isReleaseMode;
[ObservableProperty]
private IEnumerable<PackageVersion> availableVersions = new List<PackageVersion>();
[ObservableProperty]
private PackageVersion? selectedVersion;
[ObservableProperty]
private SharedFolderMethod selectedSharedFolderMethod;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowTorchVersionOptions))]
private TorchVersion selectedTorchVersion;
[ObservableProperty]
private ObservableCollection<GitCommit>? availableCommits;
[ObservableProperty]
private GitCommit? selectedCommit;
[ObservableProperty]
private ObservableCollection<ExtensionViewModel> availableExtensions = [];
private PackageVersionOptions? allOptions;
public override async Task OnLoadedAsync()
{
if (Design.IsDesignMode)
return;
OnInstallNameChanged(InstallName);
AvailableExtensions = new ObservableCollection<ExtensionViewModel>(
SelectedPackage.AvailableExtensions.Select(p => new ExtensionViewModel { Extension = p })
);
allOptions = await SelectedPackage.GetAllVersionOptions();
if (ShowReleaseMode)
{
IsReleaseMode = true;
}
else
{
UpdateVersions();
await UpdateCommits(SelectedPackage.MainBranch);
}
}
[RelayCommand]
private async Task Install()
{
if (string.IsNullOrWhiteSpace(InstallName))
{
notificationService.Show(
new Notification(
"Package name is empty",
"Please enter a name for the package",
NotificationType.Error
)
);
return;
}
var setPackageInstallingStep = new SetPackageInstallingStep(settingsManager, InstallName);
var installLocation = Path.Combine(settingsManager.LibraryDir, "Packages", InstallName);
if (Directory.Exists(installLocation))
{
var installPath = new DirectoryPath(installLocation);
await installPath.DeleteVerboseAsync(logger);
}
var prereqStep = new SetupPrerequisitesStep(prerequisiteHelper, pyRunner);
var downloadOptions = new DownloadPackageVersionOptions();
var installedVersion = new InstalledPackageVersion();
if (IsReleaseMode)
{
downloadOptions.VersionTag =
SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null");
downloadOptions.IsLatest = AvailableVersions?.First().TagName == downloadOptions.VersionTag;
downloadOptions.IsPrerelease = SelectedVersion.IsPrerelease;
installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag;
installedVersion.IsPrerelease = SelectedVersion.IsPrerelease;
}
else
{
downloadOptions.CommitHash =
SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null");
downloadOptions.BranchName =
SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null");
downloadOptions.IsLatest = AvailableCommits?.First().Sha == SelectedCommit.Sha;
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,
SelectedSharedFolderMethod,
downloadOptions,
installLocation
);
var installExtensionSteps = AvailableExtensions
.Where(e => e.IsSelected)
.Select(
e =>
new InstallExtensionStep(
e.Extension,
Path.Combine(installLocation, SelectedPackage.ExtensionsFolderName)
)
)
.ToList();
var setupModelFoldersStep = new SetupModelFoldersStep(
SelectedPackage,
SelectedSharedFolderMethod,
installLocation
);
var package = new InstalledPackage
{
DisplayName = InstallName,
LibraryPath = Path.Combine("Packages", InstallName),
Id = Guid.NewGuid(),
PackageName = SelectedPackage.Name,
Version = installedVersion,
LaunchCommand = SelectedPackage.LaunchCommand,
LastUpdateCheck = DateTimeOffset.Now,
PreferredTorchVersion = SelectedTorchVersion,
PreferredSharedFolderMethod = SelectedSharedFolderMethod
};
var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, package);
var steps = new List<IPackageStep>
{
setPackageInstallingStep,
prereqStep,
downloadStep,
installStep,
};
if (installExtensionSteps.Count > 0)
{
steps.AddRange(installExtensionSteps);
}
steps.Add(setupModelFoldersStep);
steps.Add(addInstalledPackageStep);
var runner = new PackageModificationRunner { ShowDialogOnStart = true };
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps.ToList());
if (!runner.Failed)
{
EventManager.Instance.OnInstalledPackagesChanged();
notificationService.Show(
"Package Install Complete",
$"{InstallName} installed successfully",
NotificationType.Success
);
packageNavigationService.GoBack();
packageNavigationService.GoBack();
}
}
private void UpdateVersions()
{
AvailableVersions =
IsReleaseMode && ShowReleaseMode ? allOptions.AvailableVersions : allOptions.AvailableBranches;
SelectedVersion = !IsReleaseMode
? AvailableVersions?.FirstOrDefault(x => x.TagName == SelectedPackage.MainBranch)
?? AvailableVersions?.FirstOrDefault()
: AvailableVersions?.FirstOrDefault();
}
private async Task UpdateCommits(string branchName)
{
var commits = await SelectedPackage.GetAllCommits(branchName);
if (commits != null)
{
AvailableCommits = new ObservableCollection<GitCommit>(commits);
SelectedCommit = AvailableCommits.FirstOrDefault();
}
}
partial void OnInstallNameChanged(string? value)
{
ShowDuplicateWarning = settingsManager.Settings.InstalledPackages.Any(
p => p.LibraryPath == $"Packages{Path.DirectorySeparatorChar}{value}"
);
}
partial void OnIsReleaseModeChanged(bool value)
{
UpdateVersions();
}
partial void OnSelectedVersionChanged(PackageVersion? value)
{
if (IsReleaseMode)
return;
UpdateCommits(value?.TagName ?? SelectedPackage.MainBranch).SafeFireAndForget();
}
}

37
StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs

@ -9,10 +9,12 @@ using Avalonia.Controls;
using Avalonia.Controls.Notifications; using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
@ -37,17 +39,17 @@ namespace StabilityMatrix.Avalonia.ViewModels;
/// </summary> /// </summary>
[View(typeof(PackageManagerPage))] [View(typeof(PackageManagerPage))]
[Singleton] [Singleton, ManagedService]
public partial class PackageManagerViewModel : PageViewModelBase public partial class PackageManagerViewModel : PageViewModelBase
{ {
private readonly ISettingsManager settingsManager; private readonly ISettingsManager settingsManager;
private readonly ServiceManager<ViewModelBase> dialogFactory; private readonly ServiceManager<ViewModelBase> dialogFactory;
private readonly INotificationService notificationService; private readonly INotificationService notificationService;
private readonly INavigationService<NewPackageManagerViewModel> packageNavigationService;
private readonly ILogger<PackageManagerViewModel> logger; private readonly ILogger<PackageManagerViewModel> logger;
public override string Title => "Packages"; public override string Title => "Packages";
public override IconSource IconSource => public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
/// <summary> /// <summary>
/// List of installed packages /// List of installed packages
@ -71,12 +73,14 @@ public partial class PackageManagerViewModel : PageViewModelBase
ISettingsManager settingsManager, ISettingsManager settingsManager,
ServiceManager<ViewModelBase> dialogFactory, ServiceManager<ViewModelBase> dialogFactory,
INotificationService notificationService, INotificationService notificationService,
INavigationService<NewPackageManagerViewModel> packageNavigationService,
ILogger<PackageManagerViewModel> logger ILogger<PackageManagerViewModel> logger
) )
{ {
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.dialogFactory = dialogFactory; this.dialogFactory = dialogFactory;
this.notificationService = notificationService; this.notificationService = notificationService;
this.packageNavigationService = packageNavigationService;
this.logger = logger; this.logger = logger;
EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged; EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;
@ -118,10 +122,7 @@ public partial class PackageManagerViewModel : PageViewModelBase
if (Design.IsDesignMode) if (Design.IsDesignMode)
return; return;
installedPackages.EditDiff( installedPackages.EditDiff(settingsManager.Settings.InstalledPackages, InstalledPackage.Comparer);
settingsManager.Settings.InstalledPackages,
InstalledPackage.Comparer
);
var currentUnknown = await Task.Run(IndexUnknownPackages); var currentUnknown = await Task.Run(IndexUnknownPackages);
unknownInstalledPackages.Edit(s => s.Load(currentUnknown)); unknownInstalledPackages.Edit(s => s.Load(currentUnknown));
@ -137,6 +138,9 @@ public partial class PackageManagerViewModel : PageViewModelBase
public async Task ShowInstallDialog(BasePackage? selectedPackage = null) public async Task ShowInstallDialog(BasePackage? selectedPackage = null)
{ {
NavigateToSubPage(typeof(NewInstallerDialogViewModel));
return;
var viewModel = dialogFactory.Get<InstallerViewModel>(); var viewModel = dialogFactory.Get<InstallerViewModel>();
viewModel.SelectedPackage = selectedPackage ?? viewModel.AvailablePackages[0]; viewModel.SelectedPackage = selectedPackage ?? viewModel.AvailablePackages[0];
@ -204,11 +208,7 @@ public partial class PackageManagerViewModel : PageViewModelBase
var currentPackages = settingsManager.Settings.InstalledPackages.ToImmutableArray(); var currentPackages = settingsManager.Settings.InstalledPackages.ToImmutableArray();
foreach ( foreach (var subDir in packageDir.Info.EnumerateDirectories().Select(info => new DirectoryPath(info)))
var subDir in packageDir.Info
.EnumerateDirectories()
.Select(info => new DirectoryPath(info))
)
{ {
var expectedLibraryPath = $"Packages{Path.DirectorySeparatorChar}{subDir.Name}"; var expectedLibraryPath = $"Packages{Path.DirectorySeparatorChar}{subDir.Name}";
@ -227,6 +227,19 @@ public partial class PackageManagerViewModel : PageViewModelBase
} }
} }
[RelayCommand]
private void NavigateToSubPage(Type viewModelType)
{
Dispatcher.UIThread.Post(
() =>
packageNavigationService.NavigateTo(
viewModelType,
BetterSlideNavigationTransition.PageSlideFromRight
),
DispatcherPriority.Send
);
}
private void OnInstalledPackagesChanged(object? sender, EventArgs e) => private void OnInstalledPackagesChanged(object? sender, EventArgs e) =>
OnLoadedAsync().SafeFireAndForget(); OnLoadedAsync().SafeFireAndForget();
} }

2
StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml.cs

@ -1,8 +1,10 @@
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Views.Dialogs; namespace StabilityMatrix.Avalonia.Views.Dialogs;
[Singleton]
public partial class InstallerDialog : UserControlBase public partial class InstallerDialog : UserControlBase
{ {
public InstallerDialog() public InstallerDialog()

233
StabilityMatrix.Avalonia/Views/Dialogs/NewInstallerDialog.axaml

@ -0,0 +1,233 @@
<controls:UserControlBase xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:packages="clr-namespace:StabilityMatrix.Core.Models.Packages;assembly=StabilityMatrix.Core"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="dialogs:NewInstallerDialogViewModel"
d:DataContext="{x:Static mocks:DesignData.NewInstallerDialogViewModel}"
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.NewInstallerDialog">
<controls:UserControlBase.Resources>
<input:StandardUICommand x:Key="PackageSelected"
Command="{Binding OnPackageSelected}"/>
<DataTemplate x:DataType="packages:BaseGitPackage" x:Key="PackageTemplate">
<Button Padding="0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="4"
Classes="transparent-full"
CornerRadius="8"
Command="{StaticResource PackageSelected}"
CommandParameter="{Binding }">
<controls:Card>
<Grid RowDefinitions="Auto, Auto, Auto, Auto"
ColumnDefinitions="Auto, *, Auto">
<controls:BetterAdvancedImage
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
MaxHeight="128"
MaxWidth="128"
Stretch="UniformToFill"
CornerRadius="4"
Source="{Binding PreviewImageUri}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{Binding DisplayName}"
FontWeight="Light"
Margin="16,0,0,0"
FontSize="20" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{Binding ByAuthor}"
FontWeight="Light"
Margin="16,-4,0,0"
FontSize="13" />
<TextBlock Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="1"
TextWrapping="Wrap"
Text="{Binding Blurb}"
FontWeight="Light"
Margin="16,0,0,0"
VerticalAlignment="Center"
FontSize="16" />
<ItemsRepeater Grid.Column="1"
Grid.Row="3"
Margin="16, 8, 0, 8"
ItemsSource="{Binding AvailableTorchVersions}">
<ItemsRepeater.Layout>
<StackLayout Orientation="Horizontal" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type models:TorchVersion}">
<controls:Card
Tag="{Binding }"
HorizontalAlignment="Left"
Padding="4"
Margin="0,0,8,0"
VerticalAlignment="Top">
<controls:Card.Styles>
<Style Selector="controls|Card[Tag=Cuda]">
<Setter Property="Background"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="NVIDIA"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=Rocm]">
<Setter Property="Background"
Value="{DynamicResource ThemeDarkRedColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeDarkRedColor}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="AMD (Linux)"
TextAlignment="Center"
VerticalAlignment="Center"
ToolTip.Tip="For AMD GPUs that support ROCm on Linux" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=DirectMl]">
<Setter Property="Background"
Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="DirectML"
TextAlignment="Center"
VerticalAlignment="Center"
ToolTip.Tip="For any DirectX compatible GPU on Windows" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=Mps]">
<Setter Property="Background" Value="White" />
<Setter Property="BorderBrush" Value="White" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="macOS"
TextAlignment="Center"
Foreground="Black"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=Cpu]">
<Setter Property="Background"
Value="{DynamicResource ThemeBlueGreyColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeBlueGreyColor}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="CPU"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
</controls:Card.Styles>
</controls:Card>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<controls1:SymbolIcon Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="24"
Symbol="ChevronRight" />
</Grid>
</controls:Card>
</Button>
</DataTemplate>
</controls:UserControlBase.Resources>
<Grid>
<TabControl TabStripPlacement="Top">
<TabControl.Items>
<TabItem Header="Inference" Margin="8,0,0,0">
<ScrollViewer Padding="8">
<ItemsRepeater ItemsSource="{Binding InferencePackages}">
<ItemsRepeater.Layout>
<StackLayout Orientation="Vertical" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<StaticResource ResourceKey="PackageTemplate" />
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</TabItem>
<TabItem Header="Training">
<ScrollViewer Padding="8">
<ItemsRepeater ItemsSource="{Binding TrainingPackages}">
<ItemsRepeater.Layout>
<StackLayout Orientation="Vertical" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<StaticResource ResourceKey="PackageTemplate" />
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</TabItem>
</TabControl.Items>
</TabControl>
</Grid>
</controls:UserControlBase>

13
StabilityMatrix.Avalonia/Views/Dialogs/NewInstallerDialog.axaml.cs

@ -0,0 +1,13 @@
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Views.Dialogs;
[Singleton]
public partial class NewInstallerDialog : UserControlBase
{
public NewInstallerDialog()
{
InitializeComponent();
}
}

49
StabilityMatrix.Avalonia/Views/NewPackageManagerPage.axaml

@ -0,0 +1,49 @@
<controls:UserControlBase xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia.BreadcrumbBar"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:StabilityMatrix.Avalonia"
xmlns:base="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:NewPackageManagerViewModel"
x:CompileBindings="True"
d:DataContext="{x:Static designData:DesignData.NewPackageManagerViewModel}"
x:Class="StabilityMatrix.Avalonia.Views.NewPackageManagerPage">
<controls:UserControlBase.Resources>
<!-- Override styles for BreadcrumbBar -->
<!-- ReSharper disable Xaml.RedundantResource -->
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">24</x:Double>
<x:Double x:Key="BreadcrumbBarChevronFontSize">17</x:Double>
<Thickness x:Key="BreadcrumbBarChevronPadding">6,3</Thickness>
<FontWeight x:Key="BreadcrumbBarItemFontWeight">Medium</FontWeight>
<!-- ReSharper restore Xaml.RedundantResource -->
</controls:UserControlBase.Resources>
<Grid RowDefinitions="Auto,*">
<ui:BreadcrumbBar
Grid.Row="0"
Margin="16,8"
x:Name="BreadcrumbBar"
ItemsSource="{Binding CurrentPagePath}">
<ui:BreadcrumbBar.ItemTemplate>
<DataTemplate x:DataType="base:PageViewModelBase">
<ui:BreadcrumbBarItem Content="{Binding Title}" />
</DataTemplate>
</ui:BreadcrumbBar.ItemTemplate>
</ui:BreadcrumbBar>
<controls1:Frame
Grid.Row="1"
Name="FrameView">
<controls1:Frame.NavigationPageFactory>
<avalonia:ViewLocator/>
</controls1:Frame.NavigationPageFactory>
</controls1:Frame>
</Grid>
</controls:UserControlBase>

99
StabilityMatrix.Avalonia/Views/NewPackageManagerPage.axaml.cs

@ -0,0 +1,99 @@
using System;
using System.ComponentModel;
using System.Linq;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using FluentAvalonia.UI.Navigation;
using Microsoft.Extensions.DependencyInjection;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models;
namespace StabilityMatrix.Avalonia.Views;
[Singleton]
public partial class NewPackageManagerPage : UserControlBase, IHandleNavigation
{
private readonly INavigationService<NewPackageManagerViewModel> packageNavigationService;
private bool hasLoaded;
private NewPackageManagerViewModel ViewModel => (NewPackageManagerViewModel)DataContext!;
[DesignOnly(true)]
[Obsolete("For XAML use only", true)]
public NewPackageManagerPage()
: this(App.Services.GetRequiredService<INavigationService<NewPackageManagerViewModel>>()) { }
public NewPackageManagerPage(INavigationService<NewPackageManagerViewModel> packageNavigationService)
{
this.packageNavigationService = packageNavigationService;
InitializeComponent();
packageNavigationService.SetFrame(FrameView);
packageNavigationService.TypedNavigation += NavigationService_OnTypedNavigation;
FrameView.Navigated += FrameView_Navigated;
BreadcrumbBar.ItemClicked += BreadcrumbBar_ItemClicked;
}
/// <inheritdoc />
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
if (!hasLoaded)
{
// Initial load, navigate to first page
Dispatcher.UIThread.Post(
() =>
packageNavigationService.NavigateTo(
ViewModel.SubPages[0],
new SuppressNavigationTransitionInfo()
)
);
hasLoaded = true;
}
}
private void NavigationService_OnTypedNavigation(object? sender, TypedNavigationEventArgs e)
{
ViewModel.CurrentPage =
ViewModel.SubPages.FirstOrDefault(x => x.GetType() == e.ViewModelType)
?? e.ViewModel as PageViewModelBase;
}
private void FrameView_Navigated(object? sender, NavigationEventArgs args)
{
if (args.Content is not PageViewModelBase vm)
{
return;
}
ViewModel.CurrentPage = vm;
}
private void BreadcrumbBar_ItemClicked(BreadcrumbBar sender, BreadcrumbBarItemClickedEventArgs args)
{
// Skip if already on same page
if (args.Item is not PageViewModelBase viewModel || viewModel == ViewModel.CurrentPage)
{
return;
}
packageNavigationService.NavigateTo(viewModel, BetterSlideNavigationTransition.PageSlideFromLeft);
}
public bool GoBack()
{
return packageNavigationService.GoBack();
}
}

195
StabilityMatrix.Avalonia/Views/PackageInstallDetailView.axaml

@ -0,0 +1,195 @@
<controls:UserControlBase xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:avalonia="clr-namespace:FluentIcons.Avalonia;assembly=FluentIcons.Avalonia"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:database="clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core"
xmlns:extensions="clr-namespace:StabilityMatrix.Core.Models.Packages.Extensions;assembly=StabilityMatrix.Core"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:PackageInstallDetailViewModel"
x:CompileBindings="True"
d:DataContext="{x:Static designData:DesignData.PackageInstallDetailViewModel}"
x:Class="StabilityMatrix.Avalonia.Views.PackageInstallDetailView">
<Grid ColumnDefinitions="Auto, *"
RowDefinitions="Auto, *">
<controls:BetterAdvancedImage
MaxHeight="300"
MaxWidth="300"
Margin="16,16,8,8"
CornerRadius="8"
VerticalAlignment="Top"
Stretch="UniformToFill"
Source="{Binding SelectedPackage.PreviewImageUri}" />
<StackPanel Grid.Row="0" Grid.Column="1"
Margin="8"
Orientation="Vertical">
<controls:HyperlinkIconButton NavigateUri="{Binding SelectedPackage.LicenseUrl}"
Content="{Binding SelectedPackage.LicenseType}"/>
<ToggleSwitch OnContent="{x:Static lang:Resources.Label_Releases}"
OffContent="{x:Static lang:Resources.Label_Branches}"
IsVisible="{Binding ShowReleaseMode}"
IsChecked="{Binding IsReleaseMode}"
Margin="8,0,8,8" />
<!-- Version Selector -->
<Label Margin="8,0"
FontSize="14"
FontWeight="Light"
Content="{Binding ReleaseLabelText}" />
<controls1:FAComboBox
ItemsSource="{Binding AvailableVersions}"
MinWidth="250"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontSize="16"
Margin="8,0,8,8"
SelectedItem="{Binding SelectedVersion}">
<controls1:FAComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models:PackageVersion}">
<TextBlock
Name="NameTextBlock"
VerticalAlignment="Center"
Text="{Binding TagName}" />
</DataTemplate>
</controls1:FAComboBox.ItemTemplate>
</controls1:FAComboBox>
<Label Margin="8,0"
FontSize="14"
FontWeight="Light"
ToolTip.Tip="This will also be the folder name"
Content="{x:Static lang:Resources.Label_DisplayName}" />
<TextBox MinWidth="250"
Margin="8,0"
FontSize="16"
FontWeight="SemiLight"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
ToolTip.Tip="This will also be the folder name"
Text="{Binding InstallName, Mode=TwoWay}" />
<Label Margin="8,0"
Content="{Binding FullInstallPath}" Foreground="LightGray" />
<StackPanel Orientation="Horizontal"
Margin="8,8,8,8"
IsVisible="{Binding ShowDuplicateWarning}">
<avalonia:SymbolIcon
Foreground="{DynamicResource ThemeRedColor}"
Margin="0,0,8,0"
Symbol="Alert" />
<TextBlock
Foreground="{DynamicResource ThemeRedColor}"
TextAlignment="Left"
TextWrapping="Wrap">
<Run Text="{x:Static lang:Resources.Label_InstallationWithThisNameExists}" />
<LineBreak />
<Run Text="{x:Static lang:Resources.Label_PleaseChooseDifferentName}" />
</TextBlock>
</StackPanel>
<Expander Header="Extensions"
FontSize="16"
HorizontalAlignment="Stretch"
IsVisible="{Binding ShowExtensions}"
Margin="8">
<ItemsRepeater ItemsSource="{Binding AvailableExtensions}">
<ItemsRepeater.Layout>
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4"/>
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="{x:Type viewModels:ExtensionViewModel}">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}"
Content="{Binding Extension.DisplayName}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Expander>
<Expander Header="{x:Static lang:Resources.Label_AdvancedOptions}"
FontSize="16"
HorizontalAlignment="Stretch"
Margin="8">
<Grid ColumnDefinitions="*, *, *"
RowDefinitions="Auto, Auto"
Margin="8,0">
<Label Grid.Row="0" Grid.Column="0"
Content="{x:Static lang:Resources.Label_SharedModelFolderStrategy}"
Margin="0,0,0,4" />
<controls1:FAComboBox Grid.Row="1" Grid.Column="0"
ItemsSource="{Binding SelectedPackage.AvailableSharedFolderMethods}"
MinWidth="150"
MinHeight="38"
SelectedItem="{Binding SelectedSharedFolderMethod}">
<controls1:FAComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models:SharedFolderMethod}">
<TextBlock
Margin="8,4,0,4"
Text="{Binding }" />
</DataTemplate>
</controls1:FAComboBox.ItemTemplate>
</controls1:FAComboBox>
<Label Grid.Row="0" Grid.Column="1"
Content="{x:Static lang:Resources.Label_PyTorchVersion}"
IsVisible="{Binding ShowTorchVersionOptions}"
Margin="0,0,0,4" />
<controls1:FAComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding SelectedPackage.AvailableTorchVersions}"
MinWidth="150"
MinHeight="38"
IsVisible="{Binding ShowTorchVersionOptions}"
SelectedItem="{Binding SelectedTorchVersion}">
<controls1:FAComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models:TorchVersion}">
<TextBlock
Margin="8,4,0,4"
Text="{Binding }" />
</DataTemplate>
</controls1:FAComboBox.ItemTemplate>
</controls1:FAComboBox>
<Label Grid.Row="0" Grid.Column="2"
Content="{x:Static lang:Resources.Label_Commit}"
Margin="0,0,0,4"
IsVisible="{Binding !IsReleaseMode}" />
<controls1:FAComboBox Grid.Row="1" Grid.Column="2"
IsVisible="{Binding !IsReleaseMode}"
ItemsSource="{Binding AvailableCommits}"
MinWidth="150"
MinHeight="38"
SelectedItem="{Binding SelectedCommit}">
<controls1:FAComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type database:GitCommit}">
<TextBlock
Margin="8,4,0,4"
Name="NameTextBlock"
Text="{Binding ShortSha}" />
</DataTemplate>
</controls1:FAComboBox.ItemTemplate>
</controls1:FAComboBox>
</Grid>
</Expander>
<Button Classes="success"
Margin="8,8,8,8"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontSize="16"
Command="{Binding InstallCommand}"
Content="{x:Static lang:Resources.Action_Install}" />
</StackPanel>
</Grid>
</controls:UserControlBase>

13
StabilityMatrix.Avalonia/Views/PackageInstallDetailView.axaml.cs

@ -0,0 +1,13 @@
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Views;
[Transient]
public partial class PackageInstallDetailView : UserControlBase
{
public PackageInstallDetailView()
{
InitializeComponent();
}
}

6
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -17,12 +17,6 @@
x:Class="StabilityMatrix.Avalonia.Views.PackageManagerPage"> x:Class="StabilityMatrix.Avalonia.Views.PackageManagerPage">
<Grid Margin="16" RowDefinitions="Auto,*,Auto"> <Grid Margin="16" RowDefinitions="Auto,*,Auto">
<!-- Title -->
<TextBlock Grid.Row="0" Text="{Binding Title}"
Margin="0,0,0,16"
FontSize="24" />
<!-- Cards -->
<ScrollViewer Grid.Row="1"> <ScrollViewer Grid.Row="1">
<ItemsRepeater <ItemsRepeater
ItemsSource="{Binding PackageCards}"> ItemsSource="{Binding PackageCards}">

1
StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs

@ -9,4 +9,5 @@ public interface IPackageFactory
BasePackage? FindPackageByName(string? packageName); BasePackage? FindPackageByName(string? packageName);
BasePackage? this[string packageName] { get; } BasePackage? this[string packageName] { get; }
PackagePair? GetPackagePair(InstalledPackage? installedPackage); PackagePair? GetPackagePair(InstalledPackage? installedPackage);
IEnumerable<BasePackage> GetPackagesByType(PackageType packageType);
} }

3
StabilityMatrix.Core/Helper/Factory/PackageFactory.cs

@ -39,4 +39,7 @@ public class PackageFactory : IPackageFactory
? null ? null
: new PackagePair(installedPackage, basePackage); : new PackagePair(installedPackage, basePackage);
} }
public IEnumerable<BasePackage> GetPackagesByType(PackageType packageType) =>
basePackages.Values.Where(p => p.PackageType == packageType);
} }

15
StabilityMatrix.Core/Models/PackageModification/InstallExtensionStep.cs

@ -0,0 +1,15 @@
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages.Extensions;
using StabilityMatrix.Core.Models.Progress;
namespace StabilityMatrix.Core.Models.PackageModification;
public class InstallExtensionStep(ExtensionBase extension, DirectoryPath extensionsDir) : IPackageStep
{
public Task ExecuteAsync(IProgress<ProgressReport>? progress = null)
{
return extension.InstallExtensionAsync(extensionsDir, extension.MainBranch);
}
public string ProgressTitle => $"Installing {extension.DisplayName}";
}

7
StabilityMatrix.Core/Models/PackageType.cs

@ -0,0 +1,7 @@
namespace StabilityMatrix.Core.Models;
public enum PackageType
{
SdInference,
SdTraining
}

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

@ -3,6 +3,7 @@ using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Helper.HardwareInfo;
using StabilityMatrix.Core.Models.Database; using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages.Extensions;
using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Python;
@ -47,6 +48,12 @@ public abstract class BasePackage
public abstract PackageDifficulty InstallerSortOrder { get; } public abstract PackageDifficulty InstallerSortOrder { get; }
public virtual PackageType PackageType => PackageType.SdInference;
public virtual IEnumerable<ExtensionBase> AvailableExtensions => Enumerable.Empty<ExtensionBase>();
public virtual string ExtensionsFolderName => string.Empty;
public abstract Task DownloadPackage( public abstract Task DownloadPackage(
string installLocation, string installLocation,
DownloadPackageVersionOptions versionOptions, DownloadPackageVersionOptions versionOptions,

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

@ -6,6 +6,7 @@ using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Helper.HardwareInfo;
using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages.Extensions;
using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Python;
@ -167,6 +168,9 @@ public class ComfyUI(
public override string MainBranch => "master"; public override string MainBranch => "master";
public override IEnumerable<ExtensionBase> AvailableExtensions => [new ComfyManager(PrerequisiteHelper)];
public override string ExtensionsFolderName => "custom_nodes";
public override IEnumerable<TorchVersion> AvailableTorchVersions => public override IEnumerable<TorchVersion> AvailableTorchVersions =>
new[] new[]
{ {

28
StabilityMatrix.Core/Models/Packages/Extensions/ComfyManager.cs

@ -0,0 +1,28 @@
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.FileInterfaces;
namespace StabilityMatrix.Core.Models.Packages.Extensions;
public class ComfyManager(IPrerequisiteHelper prerequisiteHelper) : ExtensionBase
{
public override string RepoName => "ComfyUI-Manager";
public override string DisplayName { get; set; } = "ComfyUI Manager";
public override string Author => "ltdrdata";
public override string Blurb =>
"ComfyUI-Manager is an extension designed to enhance the usability of ComfyUI. It offers management functions to install, remove, disable, and enable various custom nodes of ComfyUI.";
public override IEnumerable<string> CompatibleWith => [nameof(ComfyUI)];
public override string MainBranch => "main";
public override Task InstallExtensionAsync(
DirectoryPath installDirectory,
string branch,
CancellationToken cancellationToken = default
)
{
return Directory.Exists(Path.Combine(installDirectory, RepoName))
? Task.CompletedTask
: prerequisiteHelper.RunGit(new[] { "clone", GithubUrl }, installDirectory);
}
}

24
StabilityMatrix.Core/Models/Packages/Extensions/ExtensionBase.cs

@ -0,0 +1,24 @@
using StabilityMatrix.Core.Models.FileInterfaces;
namespace StabilityMatrix.Core.Models.Packages.Extensions;
public abstract class ExtensionBase
{
public string ByAuthor => $"By {Author}";
public abstract string RepoName { get; }
public abstract string DisplayName { get; set; }
public abstract string Author { get; }
public abstract string Blurb { get; }
public abstract IEnumerable<string> CompatibleWith { get; }
public abstract string MainBranch { get; }
public abstract Task InstallExtensionAsync(
DirectoryPath installDirectory,
string branch,
CancellationToken cancellationToken = default
);
public string GithubUrl => $"https://github.com/{Author}/{RepoName}";
}

4
StabilityMatrix.Core/Models/Packages/KohyaSs.cs

@ -32,7 +32,7 @@ public class KohyaSs(
public override Uri PreviewImageUri => public override Uri PreviewImageUri =>
new( new(
"https://camo.githubusercontent.com/2170d2204816f428eec57ff87218f06344e0b4d91966343a6c5f0a76df91ec75/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f6b35696d713031757655592f302e6a7067" "https://camo.githubusercontent.com/5154eea62c113d5c04393e51a0d0f76ef25a723aad29d256dcc85ead1961cd41/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f6b35696d713031757655592f302e6a7067"
); );
public override string OutputFolderName => string.Empty; public override string OutputFolderName => string.Empty;
@ -44,7 +44,7 @@ public class KohyaSs(
"Nvidia GPU with at least 8GB VRAM is recommended. May be unstable on Linux."; "Nvidia GPU with at least 8GB VRAM is recommended. May be unstable on Linux.";
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.UltraNightmare; public override PackageDifficulty InstallerSortOrder => PackageDifficulty.UltraNightmare;
public override PackageType PackageType => PackageType.SdTraining;
public override bool OfferInOneClickInstaller => false; public override bool OfferInOneClickInstaller => false;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None; public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;
public override IEnumerable<TorchVersion> AvailableTorchVersions => [TorchVersion.Cuda]; public override IEnumerable<TorchVersion> AvailableTorchVersions => [TorchVersion.Cuda];

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

@ -35,7 +35,7 @@ public class OneTrainer(
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None; public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;
public override IEnumerable<TorchVersion> AvailableTorchVersions => [TorchVersion.Cuda]; public override IEnumerable<TorchVersion> AvailableTorchVersions => [TorchVersion.Cuda];
public override bool IsCompatible => HardwareHelper.HasNvidiaGpu(); public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();
public override PackageType PackageType => PackageType.SdTraining;
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
new[] { SharedFolderMethod.None }; new[] { SharedFolderMethod.None };
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Nightmare; public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Nightmare;

Loading…
Cancel
Save