diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 53df9f3e8..1b2d547d2 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -22,6 +22,13 @@
"dotnet-csharpier"
],
"rollForward": false
+ },
+ "dotnet-script": {
+ "version": "1.6.0",
+ "commands": [
+ "dotnet-script"
+ ],
+ "rollForward": false
}
}
-}
+}
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c5bb7927c..bb4c7c4b0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,7 +21,7 @@ jobs:
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Install dependencies
run: dotnet restore
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 402d1021f..59ea95ebe 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -76,10 +76,10 @@ jobs:
echo "Using version ${{ github.event.inputs.version }}"
echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- - name: Set up .NET 8
+ - name: Set up .NET 9
uses: actions/setup-dotnet@v3
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Install PupNet
run: |
@@ -144,10 +144,10 @@ jobs:
echo "Using version ${{ github.event.inputs.version }}"
echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $env:GITHUB_ENV
- - name: Set up .NET 8
+ - name: Set up .NET 9
uses: actions/setup-dotnet@v3
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Install dependencies
run: dotnet restore
@@ -203,10 +203,10 @@ jobs:
echo "Using version ${{ github.event.inputs.version }}"
echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- - name: Set up .NET 8
+ - name: Set up .NET 9
uses: actions/setup-dotnet@v3
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Install dependencies
run: dotnet restore -p:PublishReadyToRun=true
diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml
index a0720e5d1..54930e832 100644
--- a/.github/workflows/test-ui.yml
+++ b/.github/workflows/test-ui.yml
@@ -18,7 +18,7 @@ jobs:
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Install dependencies
run: dotnet restore
diff --git a/Avalonia.Gif/Avalonia.Gif.csproj b/Avalonia.Gif/Avalonia.Gif.csproj
index 44f254430..4fd638b68 100644
--- a/Avalonia.Gif/Avalonia.Gif.csproj
+++ b/Avalonia.Gif/Avalonia.Gif.csproj
@@ -1,11 +1,9 @@
+
+
- net8.0
latest
- true
- win-x64;linux-x64;osx-x64;osx-arm64
- enable
- enable
+ true
true
true
@@ -16,9 +14,9 @@
-
-
-
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a2669171..b1b8fb1ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,136 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
+## v2.13.0
+### Added
+- Added new package - [ComfyUI-Zluda](https://github.com/patientx/ComfyUI-Zluda) - for AMD GPU users on Windows
+- Added file sizes to the Checkpoint Manager tab
+- Added the Discrete Model Sampling addon for Inference samplers, allows selecting different sampling methods, such as v_prediction, lcm, or x0, and optionally adjusts the model’s noise reduction strategy with the zero-shot noise ratio (ZSNR) toggle.
+- Added Default GPU override in Settings -> System Settings -> Default GPU
+- Added new "Copy" menu to the Inference gallery context menu, allowing you to copy generation parameters as well as the image
+- Added "StableDiffusion" folder as an option when downloading Flux models in the CivitAI model browser
+- Added support for SD3.5 in Inference
+- Added CLIP_G to HuggingFace model browser
+- Added search bar to the Installed Workflows tab
+- Added "Search with Google" and "Search with ChatGPT" to the package console output & install progress console output context menus
+- Added "Date Created" and "Date Last Modified" sorting options to the Checkpoints tab
+- Added a new "Extension Packs" section to the extension manager, allowing you to create packs for easier installation of multiple extensions at once
+- Added "Search by Creator" command to Civitai browser context menu
+- Added Beta scheduler to the scheduler selector in Inference
+- Added zipping of log files and "Show Log in Explorer" button on exceptions dialog for easier support
+- Added max concurrent downloads option & download queueing for most downloads
+- Added the ability to change the Models directory separately from the rest of the Data directory. This can be set in `Settings > Select new Models Folder`
+- Added InvokeAI model sharing option
+### Changed
+- Improved Packages Page grid layout to dynamically stretch to fill available space
+- Text Encoder / CLIP selection in Inference is now enabled via the cogwheel ⚙️ button next to the model selector
+- Updated Civitai model descriptions to properly render the interactive elements
+- Adjusted the Branch/Release toggle during package install flow to be a little more obvious
+- Updated the Dock library used for Inference - fixes some weirdness with resizing / rearranging panels
+- New file format and key derivation for protecting locally encrypted secrets (i.e. Civit / Lykos accounts) that is no longer dependent on the OS Version. This should prevent system updates from clearing account logins.
+- (Internal) Updated to .NET 9 Runtime and Avalonia 11.2.2 for performance improvements, lower memory usage, and bug fixes
+- Updated pytorch index to `rocm6.2` for AMD users of ComfyUI on Linux
+### Fixed
+- Fixed text alignment issues in the Downloads tab for certain long names / progress infos
+- Improved startup performance and resource usage with optimizations to hardware lookups. Moved reflection usages in dependency injection to source generation.
+- Fixed an issue with ComfyUI-Impact-Subpack not being installed when using FaceDetailer in Inference
+- Fixed GGUF models not showing in Inference without the GGUF extension installed (this means it will now properly prompt you to install the extension as well)
+### Supporters
+#### Visionaries
+- We're extremely grateful to our incredible Visionary-tier Patreon supporters, **Waterclouds** and **TheTekknician**! Thank you very much for your unwavering support!
+#### Pioneers
+- Many thanks to our amazing Pioneer-tier Patreon supporters, **tankfox**, **Mr Unknown**, **Szir777**, and our newest Pioneer, **NowFallenAngel**! Your generous support is very much appreciated!
+
+## v2.13.0-pre.2
+### Added
+- Added new package - [ComfyUI-Zluda](https://github.com/patientx/ComfyUI-Zluda) - for AMD GPU users on Windows
+- Added "StableDiffusion" folder as an option when downloading Flux models in the CivitAI model browser
+### Changed
+- Updated pytorch index to `rocm6.2` for AMD users of ComfyUI on Linux
+### Supporters
+#### Visionaries
+- Big shoutout to our incredible Visionary-tier Patreon supporter, **Waterclouds**! We're also delighted to introduce our newest Visionary-tier Patreon supporter, **TheTekknician**! Thank you both for your generous support!
+
+## v2.13.0-pre.1
+### Added
+- Added new package - [CogVideo](https://github.com/THUDM/CogVideo) - many thanks to @NullDev for the contribution!
+- Added file sizes to the Checkpoint Manager tab
+- Added more formatting options for Inference output filenames - thanks to @yansigit!
+- Added the Discrete Model Sampling addon for Inference samplers, allows selecting different sampling methods, such as v_prediction, lcm, or x0, and optionally adjusts the model’s noise reduction strategy with the zero-shot noise ratio (ZSNR) toggle.
+- Added Default GPU override in Settings -> System Settings -> Default GPU
+- Added the ability to copy more generation parameters from the Inference gallery context menu
+### Changed
+- Improved Packages Page grid layout to dynamically stretch to fill available space
+- New file format and key derivation for protecting locally encrypted secrets (i.e. Civit / Lykos accounts) that is no longer dependent on the OS Version. This should prevent system updates from clearing account logins.
+- (Internal) Updated to .NET 9 Runtime and Avalonia 11.2.2 for performance improvements, lower memory usage, and bug fixes
+### Fixed
+- Improved startup performance and resource usage with optimizations to hardware lookups. Moved reflection usages in dependency injection to source generation.
+- Fixed a typo in the Japanese translation - thanks to @mattyatea!
+- Fixed missing package thumbnails due to moved or inaccessible urls
+- Fixed an issue with ComfyUI-Impact-Subpack not being installed when using FaceDetailer in Inference
+- Fixed GGUF models not showing in Inference without the GGUF extension installed (this means it will now properly prompt you to install the extension as well)
+### Supporters
+#### Visionaries
+- Huge thank you to our incredible Visionary-tier Patreon supporter, **Waterclouds**! Your unwavering support is very much appreciated!
+
+## v2.13.0-dev.3
+### Added
+- Added support for SD3.5 in Inference
+- Added CLIP_G to HuggingFace model browser
+- Added search bar to the Installed Workflows tab
+- Added "Search with Google" and "Search with ChatGPT" to the package console output & install progress console output context menus
+- Added "Date Created" and "Date Last Modified" sorting options to the Checkpoints tab
+### Changed
+- Text Encoder / CLIP selection in Inference is now enabled via the cogwheel ⚙️ button next to the model selector
+- Added more base model types to the CivitAI Model Browser & Checkpoint Manager
+- Model browser base model types are now loaded dynamically from CivitAI, reducing the need for updates to add new types
+- Updated Civitai model descriptions to properly render the interactive elements
+- Updated Russian translations thanks to @vanja-san
+- Updated Simplified Chinese translations thanks to @QL-boy
+- (Internal) Updated to Avalonia 11.2.0
+### Fixed
+- Fixed some instances of Civitai model browser not loading new results
+- Fixed "Unsupported Torch Version: Cuda" errors when installing a1111
+- Fixed crash when clicking "Remind me Later" on the update dialog
+- Fixed some cases of crashing when GitHub API rate limits are exceeded
+- Fixed Git missing from env vars when running SwarmUI
+### Supporters
+#### Visionaries
+- Big shoutout to our amazing Visionary-tier Patreon supporter, **Waterclouds**! We are very grateful for your continued support!
+
+## v2.13.0-dev.2
+### Added
+- Added new package - [SimpleSDXL](https://github.com/metercai/SimpleSDXL) - many thanks to @NullDev for the contribution!
+- Added new package - [FluxGym](https://github.com/cocktailpeanut/fluxgym) - many thanks to @NullDev for the contribution!
+- Added a new "Extension Packs" section to the extension manager, allowing you to create packs for easier installation of multiple extensions at once
+- Added "Search by Creator" command to Civitai browser context menu
+- Added Beta scheduler to the scheduler selector in Inference
+- Added zipping of log files and "Show Log in Explorer" button on exceptions dialog for easier support
+- Added max concurrent downloads option & download queueing for most downloads
+### Changed
+- (Internal) Updated to Avalonia 11.1.4
+- Adjusted the Branch/Release toggle during package install flow to be a little more obvious
+- Updated the Dock library used for Inference - fixes some weirdness with resizing / rearranging panels
+### Fixed
+- Fixed ComfyUI NF4 extension not installing properly when prompted in Inference
+- Fixed [#932](https://github.com/LykosAI/StabilityMatrix/issues/932), [#935](https://github.com/LykosAI/StabilityMatrix/issues/935), [#939](https://github.com/LykosAI/StabilityMatrix/issues/939) - InvokeAI failing to update
+- Fixed repeated nested folders being created in `Models/StableDiffusion` when using Forge in Symlink mode in certain conditions. Existing folders will be repaired to their original structure on launch.
+- Fixed minimize button not working on macOS
+- Fixed InvokeAI model sharing spamming the console with "This may take awhile" in certain conditions
+- Fixed text alignment issues in the Downloads tab for certain long names / progress infos
+### Supporters
+#### Visionaries
+- A big thank you to our amazing Visionary-tier Patreon supporter, **Waterclouds**! Your continued support is invaluable!
+
+## v2.13.0-dev.1
+### Added
+- Added the ability to change the Models directory separately from the rest of the Data directory. This can be set in `Settings > Select new Models Folder`
+- Added "Copy" menu to the Inference gallery context menu, allowing you to copy the image or the seed (other params coming soon™️)
+- Added InvokeAI model sharing option
+### Supporters
+#### Visionaries
+- A heartfelt thank you to our incredible Visionary-tier Patreon supporter, **Waterclouds**! Your ongoing support means a lot to us, and we’re grateful to have you with us on this journey!
+
## v2.12.4
### Added
- Added new package - [CogVideo](https://github.com/THUDM/CogVideo) - many thanks to @NullDev for the contribution!
@@ -54,6 +184,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Fixed [#932](https://github.com/LykosAI/StabilityMatrix/issues/932), [#935](https://github.com/LykosAI/StabilityMatrix/issues/935), [#939](https://github.com/LykosAI/StabilityMatrix/issues/939) - InvokeAI failing to update
- Fixed repeated nested folders being created in `Models/StableDiffusion` when using Forge in Symlink mode in certain conditions. Existing folders will be repaired to their original structure on launch.
- Fixed minimize button not working on macOS
+
### Supporters
#### Visionaries
- We extend our heartfelt appreciation to our dedicated Visionary-tier Patreon supporter, **Waterclouds**. Your ongoing support is invaluable!
diff --git a/ConditionalSymbols.props b/ConditionalSymbols.props
new file mode 100644
index 000000000..c95b1534b
--- /dev/null
+++ b/ConditionalSymbols.props
@@ -0,0 +1,19 @@
+
+
+
+ false
+
+ CodeGeneration
+
+
+
+
+ $(DefineConstants);REGISTER_SERVICE_USAGES
+
+
+
+
+ $(DefineConstants);REGISTER_SERVICE_REFLECTION;REGISTER_SERVICE_USAGES
+
+
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 3b07e6b68..cac749720 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,19 @@
- 11.1.4
+ net9.0
+ enable
+ enable
+ true
+ true
+
+
+
+ 11.2.2
+
+
+
+
+ $(NoWarn);AVLN3001
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 000000000..f0c7fda08
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Jenkinsfile b/Jenkinsfile
index 59de2ba89..7a5ed352f 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -29,7 +29,7 @@ node("Diligence") {
}
stage('Publish Windows') {
- sh "dotnet publish ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj -c Release -o out -r win-x64 -p:PublishSingleFile=true -p:VersionPrefix=2.0.0 -p:VersionSuffix=${version} -p:IncludeNativeLibrariesForSelfExtract=true"
+ sh "dotnet publish ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj -c Release -o out -r win-x64 -p:PublishSingleFile=true -p:VersionPrefix=2.0.0 -p:VersionSuffix=${version} -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableWindowsTargeting=true"
}
stage('Publish Linux') {
diff --git a/NuGet.Config b/NuGet.Config
index 32e6bcce8..4cdf87db3 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -4,4 +4,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/Runtimes.Default.props b/Runtimes.Default.props
new file mode 100644
index 000000000..3f552ead3
--- /dev/null
+++ b/Runtimes.Default.props
@@ -0,0 +1,30 @@
+
+
+ win-x64;linux-x64;osx-x64;osx-arm64
+
+
+
+
+
+
+ win-x64
+
+
+
+
+ linux-x64
+
+
+
+
+ osx-arm64
+
+
+
+
+
+
+ net9.0-windows10.0.17763.0
+
+
diff --git a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
index 9cb9db876..b6e98faf1 100644
--- a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
+++ b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
@@ -19,13 +19,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml
index 266f72d2f..c33e54b55 100644
--- a/StabilityMatrix.Avalonia/App.axaml
+++ b/StabilityMatrix.Avalonia/App.axaml
@@ -1,99 +1,109 @@
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 700
-
-
-
- avares://StabilityMatrix.Avalonia/Assets/Fonts/NotoSansJP#Noto Sans JP
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 700
+
+
+
+
+
+ avares://StabilityMatrix.Avalonia/Assets/Fonts/NotoSansJP#Noto Sans JP
+
+
+
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs
index e9d04f488..e4d835a51 100644
--- a/StabilityMatrix.Avalonia/App.axaml.cs
+++ b/StabilityMatrix.Avalonia/App.axaml.cs
@@ -7,6 +7,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
@@ -27,6 +28,7 @@
using FluentAvalonia.Interop;
using FluentAvalonia.UI.Controls;
using MessagePipe;
+using MessagePipe.Interprocess.Workers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -79,26 +81,28 @@ public sealed class App : Application
private readonly SemaphoreSlim onExitSemaphore = new(1, 1);
+ ///
+ /// True if has started async dispose of services.
+ ///
+ private bool isAsyncDisposeStarted;
+
+ ///
+ /// True if has completed async dispose of services.
+ ///
private bool isAsyncDisposeComplete;
private bool isOnExitComplete;
- [NotNull]
- public static IServiceProvider? Services { get; private set; }
+ private ServiceProvider? serviceProvider;
[NotNull]
public static Visual? VisualRoot { get; internal set; }
- public static TopLevel TopLevel => TopLevel.GetTopLevel(VisualRoot)!;
+ public static TopLevel TopLevel => TopLevel.GetTopLevel(VisualRoot).Unwrap();
- internal static bool IsHeadlessMode =>
- TopLevel.TryGetPlatformHandle()?.HandleDescriptor is null or "STUB";
+ public static IStorageProvider StorageProvider => TopLevel.StorageProvider;
- [NotNull]
- public static IStorageProvider? StorageProvider { get; internal set; }
-
- [NotNull]
- public static IClipboard? Clipboard { get; internal set; }
+ public static IClipboard? Clipboard => TopLevel.Clipboard;
// ReSharper disable once MemberCanBePrivate.Global
[NotNull]
@@ -126,6 +130,13 @@ public sealed class App : Application
public static new App? Current => (App?)Application.Current;
+ [NotNull]
+ public static IServiceProvider? Services =>
+ Design.IsDesignMode ? DesignData.DesignData.Services : Current?.serviceProvider;
+
+ internal static bool IsHeadlessMode =>
+ TopLevel.TryGetPlatformHandle()?.HandleDescriptor is null or "STUB";
+
///
/// Called before is built.
/// Can be used by UI tests to override services.
@@ -162,7 +173,7 @@ public override void OnFrameworkInitializationCompleted()
if (Design.IsDesignMode)
{
DesignData.DesignData.Initialize();
- Services = DesignData.DesignData.Services;
+ // serviceProvider = (ServiceProvider?) DesignData.DesignData.Services;
}
else
{
@@ -288,32 +299,8 @@ private void ShowMainWindow()
if (DesktopLifetime is null)
return;
- var mainViewModel = Services.GetRequiredService();
-
var mainWindow = Services.GetRequiredService();
- mainWindow.DataContext = mainViewModel;
-
- mainWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects
- ? ExtendClientAreaChromeHints.NoChrome
- : ExtendClientAreaChromeHints.PreferSystemChrome;
-
- var settingsManager = Services.GetRequiredService();
- var windowSettings = settingsManager.Settings.WindowSettings;
- if (windowSettings != null && !Program.Args.ResetWindowPosition)
- {
- mainWindow.Position = new PixelPoint(windowSettings.X, windowSettings.Y);
- mainWindow.Width = windowSettings.Width;
- mainWindow.Height = windowSettings.Height;
- mainWindow.WindowState = windowSettings.IsMaximized ? WindowState.Maximized : WindowState.Normal;
- }
- else
- {
- mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
- }
-
VisualRoot = mainWindow;
- StorageProvider = mainWindow.StorageProvider;
- Clipboard = mainWindow.Clipboard ?? throw new NullReferenceException("Clipboard is null");
DesktopLifetime.MainWindow = mainWindow;
DesktopLifetime.Exit += OnApplicationLifetimeExit;
@@ -326,13 +313,14 @@ private void ShowMainWindow()
LogManager.AutoShutdown = false;
}
- private static void ConfigureServiceProvider()
+ [MemberNotNull(nameof(serviceProvider))]
+ private void ConfigureServiceProvider()
{
var services = ConfigureServices();
BeforeBuildServiceProvider?.Invoke(null, services);
- Services = services.BuildServiceProvider();
+ serviceProvider = services.BuildServiceProvider();
var settingsManager = Services.GetRequiredService();
@@ -394,36 +382,6 @@ internal static void ConfigurePageViewModels(IServiceCollection services)
);
}
- internal static void ConfigureDialogViewModels(IServiceCollection services, Type[] exportedTypes)
- {
- // Dialog factory
- services.AddSingleton>(provider =>
- {
- var serviceManager = new ServiceManager();
-
- var serviceManagedTypes = exportedTypes
- .Select(
- t => new { t, attributes = t.GetCustomAttributes(typeof(ManagedServiceAttribute), true) }
- )
- .Where(t1 => t1.attributes is { Length: > 0 })
- .Select(t1 => t1.t);
-
- foreach (var type in serviceManagedTypes)
- {
- if (!type.IsAssignableTo(typeof(ViewModelBase)))
- {
- throw new InvalidOperationException(
- $"Type {type.Name} with [ManagedService] attribute is not assignable to {nameof(ViewModelBase)}"
- );
- }
-
- serviceManager.Register(type, () => (ViewModelBase)provider.GetRequiredService(type));
- }
-
- return serviceManager;
- });
- }
-
internal static IServiceCollection ConfigureServices()
{
var services = new ServiceCollection();
@@ -441,89 +399,14 @@ internal static IServiceCollection ConfigureServices()
services.AddMessagePipe().AddInMemoryDistributedMessageBroker();
}
- var exportedTypes = AppDomain
- .CurrentDomain.GetAssemblies()
- .Where(a => a.FullName?.StartsWith("StabilityMatrix") == true)
- .SelectMany(a => a.GetExportedTypes())
- .ToArray();
-
- var transientTypes = exportedTypes
- .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(TransientAttribute), false) })
- .Where(
- t1 =>
- t1.attributes is { Length: > 0 }
- && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
- )
- .Select(t1 => new { Type = t1.t, Attribute = (TransientAttribute)t1.attributes[0] });
-
- foreach (var typePair in transientTypes)
- {
- if (typePair.Attribute.InterfaceType is null)
- {
- services.AddTransient(typePair.Type);
- }
- else
- {
- services.AddTransient(typePair.Attribute.InterfaceType, typePair.Type);
- }
- }
-
- var singletonTypes = exportedTypes
- .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(SingletonAttribute), false) })
- .Where(
- t1 =>
- t1.attributes is { Length: > 0 }
- && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
- )
- .Select(
- t1 => new { Type = t1.t, Attributes = t1.attributes.Cast().ToArray() }
- );
-
- foreach (var typePair in singletonTypes)
- {
- foreach (var attribute in typePair.Attributes)
- {
- if (attribute.InterfaceType is null)
- {
- services.AddSingleton(typePair.Type);
- }
- else if (attribute.ImplType is not null)
- {
- services.AddSingleton(attribute.InterfaceType, attribute.ImplType);
- }
- else
- {
- services.AddSingleton(attribute.InterfaceType, typePair.Type);
- }
-
- // IDisposable registering
- var serviceType = attribute.InterfaceType ?? typePair.Type;
-
- if (serviceType == typeof(IDisposable) || serviceType == typeof(IAsyncDisposable))
- {
- continue;
- }
-
- if (typePair.Type.IsAssignableTo(typeof(IDisposable)))
- {
- Debug.WriteLine("Registering IDisposable: {Name}", typePair.Type.Name);
- services.AddSingleton(
- provider => (IDisposable)provider.GetRequiredService(serviceType)
- );
- }
-
- if (typePair.Type.IsAssignableTo(typeof(IAsyncDisposable)))
- {
- Debug.WriteLine("Registering IAsyncDisposable: {Name}", typePair.Type.Name);
- services.AddSingleton(
- provider => (IAsyncDisposable)provider.GetRequiredService(serviceType)
- );
- }
- }
- }
+ // Register services by attributes
+ services.AddServicesByAttributes();
ConfigurePageViewModels(services);
- ConfigureDialogViewModels(services, exportedTypes);
+
+ services.AddServiceManagerWithCurrentCollectionServices(
+ s => s.ServiceType.GetCustomAttributes().Any()
+ );
// Other services
services.AddSingleton();
@@ -567,8 +450,9 @@ internal static IServiceCollection ConfigureServices()
// if (string.IsNullOrWhiteSpace(githubApiKey))
// return client;
//
- // client.Credentials =
- // new Credentials("");
+ // client.Credentials = new Credentials(
+ // ""
+ // );
return client;
});
@@ -826,44 +710,59 @@ public static void Shutdown(int exitCode = 0)
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
{
- Debug.WriteLine("Start OnShutdownRequested");
+ Logger.Trace("Start OnShutdownRequested");
if (e.Cancel)
return;
- // Check if we need to dispose IAsyncDisposables
- if (
- isAsyncDisposeComplete
- || Services.GetServices().ToList() is not { Count: > 0 } asyncDisposables
- )
+ // Skip if Async Dispose already started, shutdown will be handled by it
+ if (isAsyncDisposeStarted)
return;
- // Cancel shutdown for now
+ // Cancel shutdown for now to dispose
e.Cancel = true;
- isAsyncDisposeComplete = true;
+ isAsyncDisposeStarted = true;
- Debug.WriteLine("OnShutdownRequested Canceled: Disposing IAsyncDisposables");
+ Logger.Trace("OnShutdownRequested Canceled: Disposing IAsyncDisposables");
Dispatcher
.UIThread.InvokeAsync(async () =>
{
- foreach (var disposable in asyncDisposables)
+ if (serviceProvider is null)
{
- Debug.WriteLine($"Disposing IAsyncDisposable ({disposable.GetType().Name})");
- try
- {
- await disposable.DisposeAsync().ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Debug.Fail(ex.ToString());
- }
+ Logger.Warn("Service Provider is null, skipping Async Dispose");
+ return;
+ }
+
+ var settingsManager = Services.GetRequiredService();
+
+ Logger.Debug("Disposing App Services");
+ try
+ {
+ OnServiceProviderDisposing(serviceProvider);
+ await serviceProvider.DisposeAsync();
+ isAsyncDisposeComplete = true;
+ }
+ catch (Exception disposeEx)
+ {
+ Logger.Error(disposeEx, "Failed to dispose ServerProvider");
+ }
+
+ Logger.Debug("Flushing SettingsManager");
+ try
+ {
+ var cts = new CancellationTokenSource(5000);
+ await settingsManager.FlushAsync(cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ Logger.Error("Timeout Flushing SettingsManager");
}
})
.ContinueWith(_ =>
{
// Shutdown again
- Debug.WriteLine("Finished disposing IAsyncDisposables, shutting down");
+ Logger.Debug("Finished async shutdown tasks, shutting down");
if (Dispatcher.UIThread.SupportsRunLoops)
{
@@ -901,71 +800,19 @@ private void OnExit(object? sender, EventArgs _)
try
{
- const int timeoutTotalMs = 10000;
- const int timeoutPerDisposeMs = 2000;
-
- var timeoutTotalCts = new CancellationTokenSource(timeoutTotalMs);
-
- var toDispose = Services.GetServices().ToImmutableArray();
-
- Logger.Debug("OnExit: Preparing to Dispose {Count} Services", toDispose.Length);
-
- // Dispose IDisposable services
- foreach (var disposable in toDispose)
+ if (serviceProvider is null)
{
- Logger.Debug("OnExit: Disposing {Name}", disposable.GetType().Name);
-
- using var instanceCts = CancellationTokenSource.CreateLinkedTokenSource(
- timeoutTotalCts.Token,
- new CancellationTokenSource(timeoutPerDisposeMs).Token
- );
-
- try
- {
- Task.Run(() => disposable.Dispose(), instanceCts.Token).Wait(instanceCts.Token);
- }
- catch (OperationCanceledException)
- {
- Logger.Warn("OnExit: Timeout disposing {Name}", disposable.GetType().Name);
- }
- catch (Exception e)
- {
- Logger.Error(e, "OnExit: Failed to dispose {Name}", disposable.GetType().Name);
- }
+ Logger.Warn("Service Provider is null, skipping OnExit");
+ return;
}
- var settingsManager = Services.GetRequiredService();
-
- // If RemoveFolderLinksOnShutdown is set, delete all package junctions
- if (settingsManager is { IsLibraryDirSet: true, Settings.RemoveFolderLinksOnShutdown: true })
+ // Dispose services only if async dispose has not completed
+ if (!isAsyncDisposeComplete)
{
- Logger.Debug("OnExit: Removing package junctions");
-
- using var instanceCts = CancellationTokenSource.CreateLinkedTokenSource(
- timeoutTotalCts.Token,
- new CancellationTokenSource(timeoutPerDisposeMs).Token
- );
+ Logger.Debug("OnExit: Disposing App Services");
- try
- {
- Task.Run(
- () =>
- {
- var sharedFolders = Services.GetRequiredService();
- sharedFolders.RemoveLinksForAllPackages();
- },
- instanceCts.Token
- )
- .Wait(instanceCts.Token);
- }
- catch (OperationCanceledException)
- {
- Logger.Warn("OnExit: Timeout removing package junctions");
- }
- catch (Exception e)
- {
- Logger.Error(e, "OnExit: Failed to remove package junctions");
- }
+ OnServiceProviderDisposing(serviceProvider);
+ serviceProvider.Dispose();
}
Logger.Debug("OnExit: Finished");
@@ -979,12 +826,27 @@ private void OnExit(object? sender, EventArgs _)
}
}
+ private static void OnServiceProviderDisposing(ServiceProvider serviceProvider)
+ {
+ // Force materialize SharedFolders so its DisposeAsync is called
+ // since it's not used by anything at the moment
+ _ = serviceProvider.GetService();
+
+ // Remove the NamedPipeWorker disposable if present
+ // causes crash on avalonia dispatcher thread for some reason
+ // https://github.com/dotnet/runtime/issues/39902
+ var disposables = serviceProvider.GetDisposables();
+ disposables.RemoveAll(d => d is NamedPipeWorker);
+
+ Logger.Trace("Disposing {Count} Disposables", disposables.Count);
+ }
+
private static void TaskScheduler_UnobservedTaskException(
object? sender,
UnobservedTaskExceptionEventArgs e
)
{
- if (e.Exception is not Exception unobservedEx)
+ if (e.Observed || e.Exception is not Exception unobservedEx)
return;
try
diff --git a/StabilityMatrix.Avalonia/Assets.cs b/StabilityMatrix.Avalonia/Assets.cs
index 5a51640e0..9af4ad086 100644
--- a/StabilityMatrix.Avalonia/Assets.cs
+++ b/StabilityMatrix.Avalonia/Assets.cs
@@ -34,6 +34,9 @@ internal static class Assets
public static AvaloniaResource HfPackagesJson =>
new("avares://StabilityMatrix.Avalonia/Assets/hf-packages.json");
+ public static AvaloniaResource MarkdownCss =>
+ new("avares://StabilityMatrix.Avalonia/Assets/markdown.css");
+
private const UnixFileMode Unix755 =
UnixFileMode.UserRead
| UnixFileMode.UserWrite
diff --git a/StabilityMatrix.Avalonia/Assets/hf-packages.json b/StabilityMatrix.Avalonia/Assets/hf-packages.json
index 0307f7628..1a7a21ebf 100644
--- a/StabilityMatrix.Avalonia/Assets/hf-packages.json
+++ b/StabilityMatrix.Avalonia/Assets/hf-packages.json
@@ -932,6 +932,15 @@
],
"LicenseType": "MIT"
},
+ {
+ "ModelCategory": "Clip",
+ "ModelName": "CLIP G",
+ "RepositoryPath": "Comfy-Org/stable-diffusion-3.5-fp8",
+ "Files": [
+ "text_encoders/clip_g.safetensors"
+ ],
+ "LicenseType": "MIT"
+ },
{
"ModelCategory": "Clip",
"ModelName": "T5XXL FP16",
diff --git a/StabilityMatrix.Avalonia/Assets/markdown.css b/StabilityMatrix.Avalonia/Assets/markdown.css
new file mode 100644
index 000000000..5ec463ad4
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Assets/markdown.css
@@ -0,0 +1,1100 @@
+/* dark */
+.markdown-body {
+ color-scheme: dark;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ margin: 0;
+ color: #f0f6fc;
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+ font-size: 14px;
+ line-height: 1.5;
+ word-wrap: break-word;
+ scroll-behavior: auto !important;
+ padding: 8px;
+}
+
+.markdown-body .octicon {
+ display: inline-block;
+ fill: currentColor;
+ vertical-align: text-bottom;
+}
+
+.markdown-body h1:hover .anchor .octicon-link:before,
+.markdown-body h2:hover .anchor .octicon-link:before,
+.markdown-body h3:hover .anchor .octicon-link:before,
+.markdown-body h4:hover .anchor .octicon-link:before,
+.markdown-body h5:hover .anchor .octicon-link:before,
+.markdown-body h6:hover .anchor .octicon-link:before {
+ width: 16px;
+ height: 16px;
+ content: ' ';
+ display: inline-block;
+ background-color: currentColor;
+ -webkit-mask-image: url("data:image/svg+xml,");
+ mask-image: url("data:image/svg+xml,");
+}
+
+.markdown-body details,
+.markdown-body figcaption,
+.markdown-body figure {
+ display: block;
+}
+
+.markdown-body summary {
+ display: list-item;
+}
+
+.markdown-body [hidden] {
+ display: none !important;
+}
+
+.markdown-body a {
+ background-color: transparent;
+ color: #4493f8;
+ text-decoration: none;
+}
+
+.markdown-body abbr[title] {
+ border-bottom: none;
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+.markdown-body b,
+.markdown-body strong {
+ font-weight: 600;
+}
+
+.markdown-body dfn {
+ font-style: italic;
+}
+
+.markdown-body h1 {
+ margin: .67em 0;
+ font-weight: 600;
+ padding-bottom: .3em;
+ font-size: 2em;
+ /*border-bottom: 1px solid #3d444db3;*/
+}
+
+.markdown-body mark {
+ background-color: #bb800926;
+ color: #f0f6fc;
+}
+
+.markdown-body small {
+ font-size: 90%;
+}
+
+.markdown-body sub,
+.markdown-body sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+.markdown-body sub {
+ bottom: -0.25em;
+}
+
+.markdown-body sup {
+ top: -0.5em;
+}
+
+.markdown-body img {
+ border-style: none;
+ max-width: 100%;
+ box-sizing: content-box;
+}
+
+.markdown-body code,
+.markdown-body kbd,
+.markdown-body pre,
+.markdown-body samp {
+ font-family: monospace;
+ font-size: 1em;
+}
+
+.markdown-body figure {
+ margin: 1em 2.5rem;
+}
+
+.markdown-body hr {
+ box-sizing: content-box;
+ overflow: hidden;
+ background: transparent;
+ /*border-bottom: 1px solid #3d444db3;*/
+ height: .25em;
+ padding: 0;
+ margin: 1.5rem 0;
+ background-color: #3d444d;
+ border: 0;
+}
+
+.markdown-body input {
+ font: inherit;
+ margin: 0;
+ overflow: visible;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
+
+.markdown-body [type=button],
+.markdown-body [type=reset],
+.markdown-body [type=submit] {
+ -webkit-appearance: button;
+ appearance: button;
+}
+
+.markdown-body [type=checkbox],
+.markdown-body [type=radio] {
+ box-sizing: border-box;
+ padding: 0;
+}
+
+.markdown-body [type=number]::-webkit-inner-spin-button,
+.markdown-body [type=number]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+.markdown-body [type=search]::-webkit-search-cancel-button,
+.markdown-body [type=search]::-webkit-search-decoration {
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+.markdown-body ::-webkit-input-placeholder {
+ color: inherit;
+ opacity: .54;
+}
+
+.markdown-body ::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ appearance: button;
+ font: inherit;
+}
+
+.markdown-body a:hover {
+ text-decoration: underline;
+}
+
+.markdown-body ::placeholder {
+ color: #9198a1;
+ opacity: 1;
+}
+
+.markdown-body hr::before {
+ display: table;
+ content: "";
+}
+
+.markdown-body hr::after {
+ display: table;
+ clear: both;
+ content: "";
+}
+
+.markdown-body table {
+ border-spacing: 0;
+ border-collapse: collapse;
+ display: block;
+ width: max-content;
+ max-width: 100%;
+ overflow: auto;
+}
+
+.markdown-body td,
+.markdown-body th {
+ padding: 0;
+}
+
+.markdown-body details summary {
+ cursor: pointer;
+}
+
+.markdown-body a:focus,
+.markdown-body [role=button]:focus,
+.markdown-body input[type=radio]:focus,
+.markdown-body input[type=checkbox]:focus {
+ outline: 2px solid #1f6feb;
+ outline-offset: -2px;
+ box-shadow: none;
+}
+
+.markdown-body a:focus:not(:focus-visible),
+.markdown-body [role=button]:focus:not(:focus-visible),
+.markdown-body input[type=radio]:focus:not(:focus-visible),
+.markdown-body input[type=checkbox]:focus:not(:focus-visible) {
+ outline: solid 1px transparent;
+}
+
+.markdown-body a:focus-visible,
+.markdown-body [role=button]:focus-visible,
+.markdown-body input[type=radio]:focus-visible,
+.markdown-body input[type=checkbox]:focus-visible {
+ outline: 2px solid #1f6feb;
+ outline-offset: -2px;
+ box-shadow: none;
+}
+
+.markdown-body a:not([class]):focus,
+.markdown-body a:not([class]):focus-visible,
+.markdown-body input[type=radio]:focus,
+.markdown-body input[type=radio]:focus-visible,
+.markdown-body input[type=checkbox]:focus,
+.markdown-body input[type=checkbox]:focus-visible {
+ outline-offset: 0;
+}
+
+.markdown-body kbd {
+ display: inline-block;
+ padding: 0.25rem;
+ font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
+ line-height: 10px;
+ color: #f0f6fc;
+ vertical-align: middle;
+ background-color: #151b23;
+ border: solid 1px #3d444db3;
+ border-bottom-color: #3d444db3;
+ border-radius: 6px;
+ box-shadow: inset 0 -1px 0 #3d444db3;
+}
+
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+ margin-top: 1.5rem;
+ margin-bottom: 1rem;
+ font-weight: 600;
+ line-height: 1.25;
+}
+
+.markdown-body h2 {
+ font-weight: 600;
+ padding-bottom: .3em;
+ font-size: 1.5em;
+}
+
+.markdown-body h3 {
+ font-weight: 600;
+ font-size: 1.25em;
+ padding-bottom: .3em;
+}
+
+.markdown-body h4 {
+ font-weight: 600;
+ font-size: 1em;
+ padding-bottom: .3em;
+}
+
+.markdown-body h5 {
+ font-weight: 600;
+ font-size: .875em;
+ padding-bottom: .3em;
+}
+
+.markdown-body h6 {
+ font-weight: 600;
+ font-size: .85em;
+ color: #9198a1;
+ padding-bottom: .3em;
+}
+
+.markdown-body p {
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+
+.markdown-body blockquote {
+ margin: 0;
+ padding: 0 1em;
+ color: #9198a1;
+ border-left: .25em solid #3d444d;
+}
+
+.markdown-body ul,
+.markdown-body ol {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-left: 0.5em;
+}
+
+.markdown-body ol ol,
+.markdown-body ul ol {
+ list-style-type: lower-roman;
+}
+
+.markdown-body ul ul ol,
+.markdown-body ul ol ol,
+.markdown-body ol ul ol,
+.markdown-body ol ol ol {
+ list-style-type: lower-alpha;
+}
+
+.markdown-body dd {
+ margin-left: 0;
+}
+
+.markdown-body tt,
+.markdown-body code,
+.markdown-body samp {
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
+ font-size: 12px;
+}
+
+.markdown-body pre {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
+ font-size: 12px;
+ word-wrap: normal;
+}
+
+.markdown-body .octicon {
+ display: inline-block;
+ overflow: visible !important;
+ vertical-align: text-bottom;
+ fill: currentColor;
+}
+
+.markdown-body input::-webkit-outer-spin-button,
+.markdown-body input::-webkit-inner-spin-button {
+ margin: 0;
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+.markdown-body .mr-2 {
+ margin-right: 0.5rem !important;
+}
+
+.markdown-body::before {
+ display: table;
+ content: "";
+}
+
+.markdown-body::after {
+ display: table;
+ clear: both;
+ content: "";
+}
+
+.markdown-body>*:first-child {
+ margin-top: 0 !important;
+}
+
+.markdown-body>*:last-child {
+ margin-bottom: 0 !important;
+}
+
+.markdown-body a:not([href]) {
+ color: inherit;
+ text-decoration: none;
+}
+
+.markdown-body .absent {
+ color: #f85149;
+}
+
+.markdown-body .anchor {
+ float: left;
+ padding-right: 0.25rem;
+ margin-left: -20px;
+ line-height: 1;
+}
+
+.markdown-body .anchor:focus {
+ outline: none;
+}
+
+.markdown-body p,
+.markdown-body blockquote,
+.markdown-body ul,
+.markdown-body ol,
+.markdown-body dl,
+.markdown-body table,
+.markdown-body pre,
+.markdown-body details {
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+.markdown-body blockquote>:first-child {
+ margin-top: 0;
+}
+
+.markdown-body blockquote>:last-child {
+ margin-bottom: 0;
+}
+
+.markdown-body h1 .octicon-link,
+.markdown-body h2 .octicon-link,
+.markdown-body h3 .octicon-link,
+.markdown-body h4 .octicon-link,
+.markdown-body h5 .octicon-link,
+.markdown-body h6 .octicon-link {
+ color: #f0f6fc;
+ vertical-align: middle;
+ visibility: hidden;
+}
+
+.markdown-body h1:hover .anchor,
+.markdown-body h2:hover .anchor,
+.markdown-body h3:hover .anchor,
+.markdown-body h4:hover .anchor,
+.markdown-body h5:hover .anchor,
+.markdown-body h6:hover .anchor {
+ text-decoration: none;
+}
+
+.markdown-body h1:hover .anchor .octicon-link,
+.markdown-body h2:hover .anchor .octicon-link,
+.markdown-body h3:hover .anchor .octicon-link,
+.markdown-body h4:hover .anchor .octicon-link,
+.markdown-body h5:hover .anchor .octicon-link,
+.markdown-body h6:hover .anchor .octicon-link {
+ visibility: visible;
+}
+
+.markdown-body h1 tt,
+.markdown-body h1 code,
+.markdown-body h2 tt,
+.markdown-body h2 code,
+.markdown-body h3 tt,
+.markdown-body h3 code,
+.markdown-body h4 tt,
+.markdown-body h4 code,
+.markdown-body h5 tt,
+.markdown-body h5 code,
+.markdown-body h6 tt,
+.markdown-body h6 code {
+ padding: 0 .2em;
+ font-size: inherit;
+}
+
+.markdown-body summary h1,
+.markdown-body summary h2,
+.markdown-body summary h3,
+.markdown-body summary h4,
+.markdown-body summary h5,
+.markdown-body summary h6 {
+ display: inline-block;
+}
+
+.markdown-body summary h1 .anchor,
+.markdown-body summary h2 .anchor,
+.markdown-body summary h3 .anchor,
+.markdown-body summary h4 .anchor,
+.markdown-body summary h5 .anchor,
+.markdown-body summary h6 .anchor {
+ margin-left: -40px;
+}
+
+.markdown-body summary h1,
+.markdown-body summary h2 {
+ padding-bottom: 0;
+ border-bottom: 0;
+}
+
+.markdown-body ul.no-list,
+.markdown-body ol.no-list {
+ padding: 0;
+ list-style-type: none;
+}
+
+.markdown-body ol[type="a s"] {
+ list-style-type: lower-alpha;
+}
+
+.markdown-body ol[type="A s"] {
+ list-style-type: upper-alpha;
+}
+
+.markdown-body ol[type="i s"] {
+ list-style-type: lower-roman;
+}
+
+.markdown-body ol[type="I s"] {
+ list-style-type: upper-roman;
+}
+
+.markdown-body ol[type="1"] {
+ list-style-type: decimal;
+}
+
+.markdown-body div>ol:not([type]) {
+ list-style-type: decimal;
+}
+
+.markdown-body ul ul,
+.markdown-body ul ol,
+.markdown-body ol ol,
+.markdown-body ol ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.markdown-body li>p {
+ margin-top: 1rem;
+}
+
+.markdown-body li+li {
+ margin-top: .25em;
+}
+
+.markdown-body dl {
+ padding: 0;
+}
+
+.markdown-body dl dt {
+ padding: 0;
+ margin-top: 1rem;
+ font-size: 1em;
+ font-style: italic;
+ font-weight: 600;
+}
+
+.markdown-body dl dd {
+ padding: 0 1rem;
+ margin-bottom: 1rem;
+}
+
+.markdown-body table th {
+ font-weight: 600;
+}
+
+.markdown-body table th,
+.markdown-body table td {
+ padding: 6px 13px;
+ border: 1px solid #3d444d;
+}
+
+.markdown-body table td>:last-child {
+ margin-bottom: 0;
+}
+
+.markdown-body table tr {
+ background-color: #0d1117;
+ border-top: 1px solid #3d444db3;
+}
+
+.markdown-body table tr:nth-child(2n) {
+ background-color: #151b23;
+}
+
+.markdown-body table img {
+ background-color: transparent;
+}
+
+.markdown-body img[align=right] {
+ padding-left: 20px;
+}
+
+.markdown-body img[align=left] {
+ padding-right: 20px;
+}
+
+.markdown-body .emoji {
+ max-width: none;
+ vertical-align: text-top;
+ background-color: transparent;
+}
+
+.markdown-body span.frame {
+ display: block;
+ overflow: hidden;
+}
+
+.markdown-body span.frame>span {
+ display: block;
+ float: left;
+ width: auto;
+ padding: 7px;
+ margin: 13px 0 0;
+ overflow: hidden;
+ border: 1px solid #3d444d;
+}
+
+.markdown-body span.frame span img {
+ display: block;
+ float: left;
+}
+
+.markdown-body span.frame span span {
+ display: block;
+ padding: 5px 0 0;
+ clear: both;
+ color: #f0f6fc;
+}
+
+.markdown-body span.align-center {
+ display: block;
+ overflow: hidden;
+ clear: both;
+}
+
+.markdown-body span.align-center>span {
+ display: block;
+ margin: 13px auto 0;
+ overflow: hidden;
+ text-align: center;
+}
+
+.markdown-body span.align-center span img {
+ margin: 0 auto;
+ text-align: center;
+}
+
+.markdown-body span.align-right {
+ display: block;
+ overflow: hidden;
+ clear: both;
+}
+
+.markdown-body span.align-right>span {
+ display: block;
+ margin: 13px 0 0;
+ overflow: hidden;
+ text-align: right;
+}
+
+.markdown-body span.align-right span img {
+ margin: 0;
+ text-align: right;
+}
+
+.markdown-body span.float-left {
+ display: block;
+ float: left;
+ margin-right: 13px;
+ overflow: hidden;
+}
+
+.markdown-body span.float-left span {
+ margin: 13px 0 0;
+}
+
+.markdown-body span.float-right {
+ display: block;
+ float: right;
+ margin-left: 13px;
+ overflow: hidden;
+}
+
+.markdown-body span.float-right>span {
+ display: block;
+ margin: 13px auto 0;
+ overflow: hidden;
+ text-align: right;
+}
+
+.markdown-body code,
+.markdown-body tt {
+ padding: .2em .4em;
+ margin: 0;
+ font-size: 85%;
+ white-space: break-spaces;
+ background-color: #606c7633;
+ border-radius: 6px;
+}
+
+.markdown-body code br,
+.markdown-body tt br {
+ display: none;
+}
+
+.markdown-body del code {
+ text-decoration: inherit;
+}
+
+.markdown-body samp {
+ font-size: 85%;
+}
+
+.markdown-body pre code {
+ font-size: 100%;
+}
+
+.markdown-body pre>code {
+ padding: 0;
+ margin: 0;
+ word-break: normal;
+ white-space: pre;
+ background: transparent;
+ border: 0;
+}
+
+.markdown-body .highlight {
+ margin-bottom: 1rem;
+}
+
+.markdown-body .highlight pre {
+ margin-bottom: 0;
+ word-break: normal;
+}
+
+.markdown-body .highlight pre,
+.markdown-body pre {
+ padding: 1rem;
+ overflow: auto;
+ font-size: 85%;
+ line-height: 1.45;
+ color: #f0f6fc;
+ background-color: #151b23;
+ border-radius: 6px;
+}
+
+.markdown-body pre code,
+.markdown-body pre tt {
+ display: inline;
+ max-width: auto;
+ padding: 0;
+ margin: 0;
+ overflow: visible;
+ line-height: inherit;
+ word-wrap: normal;
+ background-color: transparent;
+ border: 0;
+}
+
+.markdown-body .csv-data td,
+.markdown-body .csv-data th {
+ padding: 5px;
+ overflow: hidden;
+ font-size: 12px;
+ line-height: 1;
+ text-align: left;
+ white-space: nowrap;
+}
+
+.markdown-body .csv-data .blob-num {
+ padding: 10px 0.5rem 9px;
+ text-align: right;
+ background: #0d1117;
+ border: 0;
+}
+
+.markdown-body .csv-data tr {
+ border-top: 0;
+}
+
+.markdown-body .csv-data th {
+ font-weight: 600;
+ background: #151b23;
+ border-top: 0;
+}
+
+.markdown-body [data-footnote-ref]::before {
+ content: "[";
+}
+
+.markdown-body [data-footnote-ref]::after {
+ content: "]";
+}
+
+.markdown-body .footnotes {
+ font-size: 12px;
+ color: #9198a1;
+ border-top: 1px solid #3d444d;
+}
+
+.markdown-body .footnotes ol {
+ padding-left: 1rem;
+}
+
+.markdown-body .footnotes ol ul {
+ display: inline-block;
+ padding-left: 1rem;
+ margin-top: 1rem;
+}
+
+.markdown-body .footnotes li {
+ position: relative;
+}
+
+.markdown-body .footnotes li:target::before {
+ position: absolute;
+ top: calc(0.5rem*-1);
+ right: calc(0.5rem*-1);
+ bottom: calc(0.5rem*-1);
+ left: calc(1.5rem*-1);
+ pointer-events: none;
+ content: "";
+ border: 2px solid #1f6feb;
+ border-radius: 6px;
+}
+
+.markdown-body .footnotes li:target {
+ color: #f0f6fc;
+}
+
+.markdown-body .footnotes .data-footnote-backref g-emoji {
+ font-family: monospace;
+}
+
+.markdown-body .pl-c {
+ color: #9198a1;
+}
+
+.markdown-body .pl-c1,
+.markdown-body .pl-s .pl-v {
+ color: #79c0ff;
+}
+
+.markdown-body .pl-e,
+.markdown-body .pl-en {
+ color: #d2a8ff;
+}
+
+.markdown-body .pl-smi,
+.markdown-body .pl-s .pl-s1 {
+ color: #f0f6fc;
+}
+
+.markdown-body .pl-ent {
+ color: #7ee787;
+}
+
+.markdown-body .pl-k {
+ color: #ff7b72;
+}
+
+.markdown-body .pl-s,
+.markdown-body .pl-pds,
+.markdown-body .pl-s .pl-pse .pl-s1,
+.markdown-body .pl-sr,
+.markdown-body .pl-sr .pl-cce,
+.markdown-body .pl-sr .pl-sre,
+.markdown-body .pl-sr .pl-sra {
+ color: #a5d6ff;
+}
+
+.markdown-body .pl-v,
+.markdown-body .pl-smw {
+ color: #ffa657;
+}
+
+.markdown-body .pl-bu {
+ color: #f85149;
+}
+
+.markdown-body .pl-ii {
+ color: #f0f6fc;
+ background-color: #8e1519;
+}
+
+.markdown-body .pl-c2 {
+ color: #f0f6fc;
+ background-color: #b62324;
+}
+
+.markdown-body .pl-sr .pl-cce {
+ font-weight: bold;
+ color: #7ee787;
+}
+
+.markdown-body .pl-ml {
+ color: #f2cc60;
+}
+
+.markdown-body .pl-mh,
+.markdown-body .pl-mh .pl-en,
+.markdown-body .pl-ms {
+ font-weight: bold;
+ color: #1f6feb;
+}
+
+.markdown-body .pl-mi {
+ font-style: italic;
+ color: #f0f6fc;
+}
+
+.markdown-body .pl-mb {
+ font-weight: bold;
+ color: #f0f6fc;
+}
+
+.markdown-body .pl-md {
+ color: #ffdcd7;
+ background-color: #67060c;
+}
+
+.markdown-body .pl-mi1 {
+ color: #aff5b4;
+ background-color: #033a16;
+}
+
+.markdown-body .pl-mc {
+ color: #ffdfb6;
+ background-color: #5a1e02;
+}
+
+.markdown-body .pl-mi2 {
+ color: #f0f6fc;
+ background-color: #1158c7;
+}
+
+.markdown-body .pl-mdr {
+ font-weight: bold;
+ color: #d2a8ff;
+}
+
+.markdown-body .pl-ba {
+ color: #9198a1;
+}
+
+.markdown-body .pl-sg {
+ color: #3d444d;
+}
+
+.markdown-body .pl-corl {
+ text-decoration: underline;
+ color: #a5d6ff;
+}
+
+.markdown-body [role=button]:focus:not(:focus-visible),
+.markdown-body [role=tabpanel][tabindex="0"]:focus:not(:focus-visible),
+.markdown-body button:focus:not(:focus-visible),
+.markdown-body summary:focus:not(:focus-visible),
+.markdown-body a:focus:not(:focus-visible) {
+ outline: none;
+ box-shadow: none;
+}
+
+.markdown-body [tabindex="0"]:focus:not(:focus-visible),
+.markdown-body details-dialog:focus:not(:focus-visible) {
+ outline: none;
+}
+
+.markdown-body g-emoji {
+ display: inline-block;
+ min-width: 1ch;
+ font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
+ font-size: 1em;
+ font-style: normal !important;
+ font-weight: 400;
+ line-height: 1;
+ vertical-align: -0.075em;
+}
+
+.markdown-body g-emoji img {
+ width: 1em;
+ height: 1em;
+}
+
+.markdown-body .task-list-item {
+ list-style-type: none;
+}
+
+.markdown-body .task-list-item label {
+ font-weight: 400;
+}
+
+.markdown-body .task-list-item.enabled label {
+ cursor: pointer;
+}
+
+.markdown-body .task-list-item+.task-list-item {
+ margin-top: 0.25rem;
+}
+
+.markdown-body .task-list-item .handle {
+ display: none;
+}
+
+.markdown-body .task-list-item-checkbox {
+ margin: 0 .2em .25em -1.4em;
+ vertical-align: middle;
+}
+
+.markdown-body ul:dir(rtl) .task-list-item-checkbox {
+ margin: 0 -1.6em .25em .2em;
+}
+
+.markdown-body ol:dir(rtl) .task-list-item-checkbox {
+ margin: 0 -1.6em .25em .2em;
+}
+
+.markdown-body .contains-task-list:hover .task-list-item-convert-container,
+.markdown-body .contains-task-list:focus-within .task-list-item-convert-container {
+ display: block;
+ width: auto;
+ height: 24px;
+ overflow: visible;
+ clip: auto;
+}
+
+.markdown-body ::-webkit-calendar-picker-indicator {
+ filter: invert(50%);
+}
+
+.markdown-body .markdown-alert {
+ padding: 0.5rem 1rem;
+ margin-bottom: 1rem;
+ color: inherit;
+ border-left: .25em solid #3d444d;
+}
+
+.markdown-body .markdown-alert>:first-child {
+ margin-top: 0;
+}
+
+.markdown-body .markdown-alert>:last-child {
+ margin-bottom: 0;
+}
+
+.markdown-body .markdown-alert .markdown-alert-title {
+ display: flex;
+ font-weight: 500;
+ align-items: center;
+ line-height: 1;
+}
+
+.markdown-body .markdown-alert.markdown-alert-note {
+ border-left-color: #1f6feb;
+}
+
+.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title {
+ color: #4493f8;
+}
+
+.markdown-body .markdown-alert.markdown-alert-important {
+ border-left-color: #8957e5;
+}
+
+.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title {
+ color: #ab7df8;
+}
+
+.markdown-body .markdown-alert.markdown-alert-warning {
+ border-left-color: #9e6a03;
+}
+
+.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title {
+ color: #d29922;
+}
+
+.markdown-body .markdown-alert.markdown-alert-tip {
+ border-left-color: #238636;
+}
+
+.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title {
+ color: #3fb950;
+}
+
+.markdown-body .markdown-alert.markdown-alert-caution {
+ border-left-color: #da3633;
+}
+
+.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title {
+ color: #f85149;
+}
+
+.markdown-body>*:first-child>.heading-element:first-child {
+ margin-top: 0 !important;
+}
diff --git a/StabilityMatrix.Avalonia/Controls/ApplicationSplashScreen.cs b/StabilityMatrix.Avalonia/Controls/ApplicationSplashScreen.cs
new file mode 100644
index 000000000..21b8cb9ab
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/ApplicationSplashScreen.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Media;
+using FluentAvalonia.UI.Windowing;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+internal class ApplicationSplashScreen : IApplicationSplashScreen
+{
+ public string? AppName { get; init; }
+
+ public IImage? AppIcon { get; init; }
+
+ public object? SplashScreenContent { get; init; }
+
+ public int MinimumShowTime { get; init; }
+
+ public Func? InitApp { get; init; }
+
+ public Task RunTasks(CancellationToken cancellationToken)
+ {
+ return InitApp?.Invoke(cancellationToken) ?? Task.CompletedTask;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml b/StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml
new file mode 100644
index 000000000..20f010b64
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml.cs b/StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml.cs
new file mode 100644
index 000000000..12fed3ab7
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using Avalonia.Logging;
+using CommunityToolkit.Mvvm.Input;
+using Nito.Disposables.Internals;
+using StabilityMatrix.Avalonia.Controls.Models;
+using StabilityMatrix.Core.Git;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+[Localizable(false)]
+public partial class GitVersionSelector : TemplatedControl
+{
+ public static readonly StyledProperty GitVersionProviderProperty =
+ AvaloniaProperty.Register(nameof(GitVersionProvider));
+
+ public IGitVersionProvider? GitVersionProvider
+ {
+ get => GetValue(GitVersionProviderProperty);
+ set => SetValue(GitVersionProviderProperty, value);
+ }
+
+ public static readonly StyledProperty BranchSelectionModeProperty =
+ AvaloniaProperty.Register(nameof(BranchSelectionMode));
+
+ public SelectionMode BranchSelectionMode
+ {
+ get => GetValue(BranchSelectionModeProperty);
+ set => SetValue(BranchSelectionModeProperty, value);
+ }
+
+ public static readonly StyledProperty CommitSelectionModeProperty =
+ AvaloniaProperty.Register(nameof(CommitSelectionMode));
+
+ public SelectionMode CommitSelectionMode
+ {
+ get => GetValue(CommitSelectionModeProperty);
+ set => SetValue(CommitSelectionModeProperty, value);
+ }
+
+ public static readonly StyledProperty TagSelectionModeProperty = AvaloniaProperty.Register<
+ GitVersionSelector,
+ SelectionMode
+ >(nameof(TagSelectionMode));
+
+ public SelectionMode TagSelectionMode
+ {
+ get => GetValue(TagSelectionModeProperty);
+ set => SetValue(TagSelectionModeProperty, value);
+ }
+
+ public static readonly StyledProperty DefaultBranchProperty = AvaloniaProperty.Register<
+ GitVersionSelector,
+ string?
+ >(nameof(DefaultBranch), "main");
+
+ ///
+ /// The default branch to use when no branch is selected. Shows as a placeholder.
+ ///
+ public string? DefaultBranch
+ {
+ get => GetValue(DefaultBranchProperty);
+ set => SetValue(DefaultBranchProperty, value);
+ }
+
+ public static readonly StyledProperty DefaultCommitProperty = AvaloniaProperty.Register<
+ GitVersionSelector,
+ string?
+ >(nameof(DefaultCommit), "latest");
+
+ ///
+ /// The default commit to use when no commit is selected. Shows as a placeholder.
+ ///
+ public string? DefaultCommit
+ {
+ get => GetValue(DefaultCommitProperty);
+ set => SetValue(DefaultCommitProperty, value);
+ }
+
+ public static readonly StyledProperty> BranchSourceProperty =
+ AvaloniaProperty.Register>(nameof(BranchSource), []);
+
+ public IReadOnlyList BranchSource
+ {
+ get => GetValue(BranchSourceProperty);
+ set => SetValue(BranchSourceProperty, value);
+ }
+
+ public static readonly StyledProperty> CommitSourceProperty =
+ AvaloniaProperty.Register>(nameof(CommitSource), []);
+
+ public IReadOnlyList CommitSource
+ {
+ get => GetValue(CommitSourceProperty);
+ set => SetValue(CommitSourceProperty, value);
+ }
+
+ public static readonly StyledProperty> TagSourceProperty =
+ AvaloniaProperty.Register>(nameof(TagSource), []);
+
+ public IReadOnlyList TagSource
+ {
+ get => GetValue(TagSourceProperty);
+ set => SetValue(TagSourceProperty, value);
+ }
+
+ public static readonly StyledProperty SelectedBranchProperty = AvaloniaProperty.Register<
+ GitVersionSelector,
+ string?
+ >(nameof(SelectedBranch), defaultBindingMode: BindingMode.TwoWay);
+
+ public string? SelectedBranch
+ {
+ get => GetValue(SelectedBranchProperty);
+ set => SetValue(SelectedBranchProperty, value);
+ }
+
+ public static readonly StyledProperty SelectedCommitProperty = AvaloniaProperty.Register<
+ GitVersionSelector,
+ string?
+ >(nameof(SelectedCommit), defaultBindingMode: BindingMode.TwoWay);
+
+ public string? SelectedCommit
+ {
+ get => GetValue(SelectedCommitProperty);
+ set => SetValue(SelectedCommitProperty, value);
+ }
+
+ public static readonly StyledProperty SelectedTagProperty = AvaloniaProperty.Register<
+ GitVersionSelector,
+ string?
+ >(nameof(SelectedTag), defaultBindingMode: BindingMode.TwoWay);
+
+ public string? SelectedTag
+ {
+ get => GetValue(SelectedTagProperty);
+ set => SetValue(SelectedTagProperty, value);
+ }
+
+ public static readonly StyledProperty SelectedVersionTypeProperty =
+ AvaloniaProperty.Register(
+ nameof(SelectedVersionType),
+ defaultBindingMode: BindingMode.TwoWay
+ );
+
+ public GitVersionSelectorVersionType SelectedVersionType
+ {
+ get => GetValue(SelectedVersionTypeProperty);
+ set => SetValue(SelectedVersionTypeProperty, value);
+ }
+
+ public static readonly DirectProperty<
+ GitVersionSelector,
+ IAsyncRelayCommand
+ > PopulateBranchesCommandProperty = AvaloniaProperty.RegisterDirect<
+ GitVersionSelector,
+ IAsyncRelayCommand
+ >(nameof(PopulateBranchesCommand), o => o.PopulateBranchesCommand);
+
+ public static readonly DirectProperty<
+ GitVersionSelector,
+ IAsyncRelayCommand
+ > PopulateCommitsForCurrentBranchCommandProperty = AvaloniaProperty.RegisterDirect<
+ GitVersionSelector,
+ IAsyncRelayCommand
+ >(nameof(PopulateCommitsForCurrentBranchCommand), o => o.PopulateCommitsForCurrentBranchCommand);
+
+ public static readonly DirectProperty<
+ GitVersionSelector,
+ IAsyncRelayCommand
+ > PopulateTagsCommandProperty = AvaloniaProperty.RegisterDirect(
+ nameof(PopulateTagsCommand),
+ o => o.PopulateTagsCommand
+ );
+
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+
+ if (GitVersionProvider is not null)
+ {
+ PopulateBranchesCommand.Execute(null);
+
+ if (SelectedBranch is not null)
+ {
+ PopulateCommitsForCurrentBranchCommand.Execute(null);
+ }
+
+ PopulateTagsCommand.Execute(null);
+ }
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ // On branch change, fetch commits
+ if (change.Property == SelectedBranchProperty)
+ {
+ PopulateCommitsForCurrentBranchCommand.Execute(null);
+ }
+ }
+
+ [RelayCommand]
+ public async Task PopulateBranches()
+ {
+ if (GitVersionProvider is null)
+ return;
+
+ var branches = await GitVersionProvider.FetchBranchesAsync();
+
+ BranchSource = branches.Select(v => v.Branch).WhereNotNull().ToImmutableList();
+ }
+
+ [RelayCommand]
+ public async Task PopulateCommitsForCurrentBranch()
+ {
+ if (string.IsNullOrEmpty(SelectedBranch))
+ {
+ CommitSource = [];
+ return;
+ }
+
+ if (GitVersionProvider is null)
+ return;
+
+ try
+ {
+ var commits = await GitVersionProvider.FetchCommitsAsync(SelectedBranch);
+
+ CommitSource = commits.Select(v => v.CommitSha).WhereNotNull().ToImmutableList();
+ }
+ catch (Exception e)
+ {
+ Logger
+ .TryGet(LogEventLevel.Error, nameof(GitVersionSelector))
+ ?.Log(this, "Failed to fetch commits for branch {Branch}: {Exception}", SelectedBranch, e);
+ }
+ }
+
+ [RelayCommand]
+ public async Task PopulateTags()
+ {
+ if (GitVersionProvider is null)
+ return;
+
+ var tags = await GitVersionProvider.FetchTagsAsync();
+
+ TagSource = tags.Select(v => v.Tag).WhereNotNull().ToImmutableList();
+ }
+
+ public enum SelectionMode
+ {
+ Disabled,
+ Required,
+ Optional
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml.cs
index 92e65bbd0..5e8ce0fa7 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class BatchSizeCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml.cs
index e085a9efa..52690492d 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class ControlNetCard : TemplatedControl;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml
new file mode 100644
index 000000000..e87de03e8
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml.cs
new file mode 100644
index 000000000..7ea6d099d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml.cs
@@ -0,0 +1,7 @@
+using Avalonia.Controls.Primitives;
+using Injectio.Attributes;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+[RegisterTransient]
+public class DiscreteModelSamplingCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ExtraNetworkCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/ExtraNetworkCard.axaml.cs
index b5e9d41f2..2f3d6631b 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ExtraNetworkCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ExtraNetworkCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class ExtraNetworkCard : TemplatedControl;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml.cs
index 6bd2853af..945eb4e42 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class FaceDetailerCard : TemplatedControl;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml.cs
index 500eaf7a1..11dcf8491 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class FreeUCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml
index 5609a5f71..841d2ace8 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml
@@ -2,16 +2,16 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls"
+ xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:dbModels="clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core"
xmlns:input="using:FluentAvalonia.UI.Input"
+ xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
- xmlns:ui="using:FluentAvalonia.UI.Controls"
- xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll"
- xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
+ xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vendorLabs="clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs"
- xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
+ xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
x:DataType="vmInference:ImageFolderCardViewModel">
@@ -21,7 +21,7 @@
-
+
@@ -48,12 +48,12 @@
+ Watermark="{x:Static lang:Resources.Label_SearchEllipsis}" />
+ IconSource="Image"
+ Label="Image" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -135,10 +175,33 @@
Command="{StaticResource ImageDeleteCommand}"
CommandParameter="{Binding $self.DataContext}"
HotKey="{x:Null}" />
-
+
+
+
+
+
+
+
+
+
@@ -170,18 +233,18 @@
-
-
+
@@ -208,8 +271,8 @@
]
public class ImageFolderCard : DropTargetTemplatedControlBase
{
private ItemsRepeater? imageRepeater;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml.cs
index cef9024ec..eaac89e17 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class ImageGalleryCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs
index a8973a3f8..a1447d2c4 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class LayerDiffuseCard : TemplatedControl;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
index 2e9a177ca..720aaec02 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
@@ -14,7 +14,10 @@
x:DataType="inference:ModelCardViewModel">
-
+
@@ -44,7 +47,7 @@
-
+
+
+ IsVisible="{Binding EnableModelLoaderSelection}"
+ Text="Model Loader" />
-
+
-
-
+
+
-
+
@@ -238,62 +245,102 @@
-
-
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
-
+ Grid.ColumnSpan="3"
+ IsVisible="{Binding IsExtraNetworksEnabled}">
+
-
+
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml.cs
index 3d3594fbf..dd355a400 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class ModelCard : TemplatedControl;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml.cs
index 6f590610d..707765721 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml.cs
@@ -5,14 +5,14 @@
using AvaloniaEdit;
using AvaloniaEdit.Editing;
using AvaloniaEdit.Utils;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Helpers;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.ViewModels.Inference;
-using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class PromptCard : TemplatedControlBase
{
///
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs
index b85591095..bdfe846d8 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class PromptExpansionCard : TemplatedControl;
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml.cs
index ace972b14..9440987e5 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class SamplerCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml.cs
index 06d164e9c..6a24457ab 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class SeedCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml.cs
index c743b028e..71b8bd7bf 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml.cs
@@ -4,13 +4,13 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using DynamicData.Binding;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.ViewModels.Inference;
-using StabilityMatrix.Core.Attributes;
using Size = Avalonia.Size;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class SelectImageCard : DropTargetTemplatedControlBase
{
///
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml.cs
index 4c2a250d2..a221a573f 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class SharpenCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml
index f2d3f7af7..84fe49239 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml
@@ -1,42 +1,36 @@
-
-
+
+
-
-
-
+
+
+
+
-
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/MarkdownViewer.axaml.cs b/StabilityMatrix.Avalonia/Controls/MarkdownViewer.axaml.cs
new file mode 100644
index 000000000..35fc033e0
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/MarkdownViewer.axaml.cs
@@ -0,0 +1,87 @@
+using System.IO;
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using Markdig;
+using TheArtOfDev.HtmlRenderer.Avalonia;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+public class MarkdownViewer : TemplatedControl
+{
+ public static readonly StyledProperty TextProperty = AvaloniaProperty.Register<
+ MarkdownViewer,
+ string
+ >(nameof(Text));
+
+ public string Text
+ {
+ get => GetValue(TextProperty);
+ set
+ {
+ SetValue(TextProperty, value);
+ ParseText(value);
+ }
+ }
+
+ public static readonly StyledProperty HtmlProperty = AvaloniaProperty.Register<
+ MarkdownViewer,
+ string
+ >(nameof(Html));
+
+ private string Html
+ {
+ get => GetValue(HtmlProperty);
+ set => SetValue(HtmlProperty, value);
+ }
+
+ public static readonly StyledProperty CustomCssProperty = AvaloniaProperty.Register<
+ MarkdownViewer,
+ string
+ >(nameof(CustomCss));
+
+ public string CustomCss
+ {
+ get => GetValue(CustomCssProperty);
+ set => SetValue(CustomCssProperty, value);
+ }
+
+ private void ParseText(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return;
+
+ var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
+ var html =
+ $"""{Markdig.Markdown.ToHtml(value, pipeline)}""";
+ Html = html;
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ if (e.NameScope.Find("PART_HtmlPanel") is not HtmlPanel htmlPanel)
+ return;
+
+ using var cssFile = Assets.MarkdownCss.Open();
+ using var reader = new StreamReader(cssFile);
+ var css = reader.ReadToEnd();
+
+ htmlPanel.BaseStylesheet = $"{css}\n{CustomCss}";
+
+ if (string.IsNullOrWhiteSpace(Html) && !string.IsNullOrWhiteSpace(Text))
+ {
+ ParseText(Text);
+ }
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ if (change.Property == TextProperty && change.NewValue != null)
+ {
+ ParseText(change.NewValue.ToString());
+ }
+
+ base.OnPropertyChanged(change);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/Models/GitVersionSelectorVersionType.cs b/StabilityMatrix.Avalonia/Controls/Models/GitVersionSelectorVersionType.cs
new file mode 100644
index 000000000..8309a2dba
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Models/GitVersionSelectorVersionType.cs
@@ -0,0 +1,7 @@
+namespace StabilityMatrix.Avalonia.Controls.Models;
+
+public enum GitVersionSelectorVersionType
+{
+ BranchCommit,
+ Tag
+}
diff --git a/StabilityMatrix.Avalonia/Controls/RefreshBadge.axaml.cs b/StabilityMatrix.Avalonia/Controls/RefreshBadge.axaml.cs
index 1943c3ae9..41618d58e 100644
--- a/StabilityMatrix.Avalonia/Controls/RefreshBadge.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/RefreshBadge.axaml.cs
@@ -1,9 +1,9 @@
using Avalonia.Markup.Xaml;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public partial class RefreshBadge : UserControlBase
{
public RefreshBadge()
diff --git a/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml.cs
index 703475126..0a34a7509 100644
--- a/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class VideoGenerationSettingsCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml.cs
index a730a993b..c64e2c4bb 100644
--- a/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls.Primitives;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
-[Transient]
+[RegisterTransient]
public class VideoOutputSettingsCard : TemplatedControl { }
diff --git a/StabilityMatrix.Avalonia/Converters/EnumToIntConverter.cs b/StabilityMatrix.Avalonia/Converters/EnumToIntConverter.cs
new file mode 100644
index 000000000..dc2a784bb
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Converters/EnumToIntConverter.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using Avalonia.Data.Converters;
+using KGySoft.CoreLibraries;
+
+namespace StabilityMatrix.Avalonia.Converters;
+
+public class EnumToIntConverter : IValueConverter
+ where TEnum : struct, Enum
+{
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is TEnum enumValue)
+ {
+ return System.Convert.ToInt32(enumValue);
+ }
+
+ return null;
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is int intValue)
+ {
+ return Unsafe.As(ref intValue);
+ }
+
+ return null;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs b/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs
new file mode 100644
index 000000000..59a7bc5d5
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Data.Converters;
+
+namespace StabilityMatrix.Avalonia.Converters;
+
+[SuppressMessage("ReSharper", "LocalizableElement")]
+public static class FileSizeConverters
+{
+ public static FuncValueConverter
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -154,6 +149,7 @@
+
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
index 4595e87ce..d8bc61426 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs
@@ -22,6 +22,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base;
[JsonDerivedType(typeof(ExtraNetworkCardViewModel), ExtraNetworkCardViewModel.ModuleKey)]
[JsonDerivedType(typeof(LayerDiffuseCardViewModel), LayerDiffuseCardViewModel.ModuleKey)]
[JsonDerivedType(typeof(FaceDetailerViewModel), FaceDetailerViewModel.ModuleKey)]
+[JsonDerivedType(typeof(DiscreteModelSamplingCardViewModel), DiscreteModelSamplingCardViewModel.ModuleKey)]
[JsonDerivedType(typeof(FreeUModule))]
[JsonDerivedType(typeof(HiresFixModule))]
[JsonDerivedType(typeof(FluxHiresFixModule))]
@@ -33,6 +34,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base;
[JsonDerivedType(typeof(LayerDiffuseModule))]
[JsonDerivedType(typeof(FaceDetailerModule))]
[JsonDerivedType(typeof(FluxGuidanceModule))]
+[JsonDerivedType(typeof(DiscreteModelSamplingModule))]
public abstract class LoadableViewModelBase : ViewModelBase, IJsonLoadableState
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/PausableProgressItemViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/PausableProgressItemViewModelBase.cs
index 2d0f26723..0881e65d4 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/PausableProgressItemViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/PausableProgressItemViewModelBase.cs
@@ -10,37 +10,47 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base;
public abstract partial class PausableProgressItemViewModelBase : ProgressItemViewModelBase
{
[ObservableProperty]
- [NotifyPropertyChangedFor(nameof(IsPaused), nameof(IsCompleted), nameof(CanPauseResume), nameof(CanCancel))]
- private ProgressState state = ProgressState.Inactive;
-
+ [NotifyPropertyChangedFor(
+ nameof(IsPaused),
+ nameof(IsCompleted),
+ nameof(CanPauseResume),
+ nameof(CanCancel)
+ )]
+ private ProgressState state = ProgressState.Inactive;
+
///
/// Whether the progress is paused
///
- public bool IsPaused => State == ProgressState.Inactive;
+ public bool IsPaused => State is ProgressState.Inactive or ProgressState.Paused;
+ public bool IsPending => State == ProgressState.Pending;
///
/// Whether the progress has succeeded, failed or was cancelled
///
- public override bool IsCompleted => State is ProgressState.Success or ProgressState.Failed or ProgressState.Cancelled;
+ public override bool IsCompleted =>
+ State is ProgressState.Success or ProgressState.Failed or ProgressState.Cancelled;
public virtual bool SupportsPauseResume => true;
public virtual bool SupportsCancel => true;
-
- public bool CanPauseResume => SupportsPauseResume && !IsCompleted;
+
+ public bool CanPauseResume => SupportsPauseResume && !IsCompleted && !IsPending;
public bool CanCancel => SupportsCancel && !IsCompleted;
-
+
private AsyncRelayCommand? pauseCommand;
public IAsyncRelayCommand PauseCommand => pauseCommand ??= new AsyncRelayCommand(Pause);
+
public virtual Task Pause() => Task.CompletedTask;
-
+
private AsyncRelayCommand? resumeCommand;
public IAsyncRelayCommand ResumeCommand => resumeCommand ??= new AsyncRelayCommand(Resume);
+
public virtual Task Resume() => Task.CompletedTask;
-
+
private AsyncRelayCommand? cancelCommand;
public IAsyncRelayCommand CancelCommand => cancelCommand ??= new AsyncRelayCommand(Cancel);
+
public virtual Task Cancel() => Task.CompletedTask;
-
+
[RelayCommand]
private Task TogglePauseResume()
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs
index 0d06e87fc..1ef8eac1c 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs
@@ -16,5 +16,5 @@ public abstract partial class ProgressItemViewModelBase : ViewModelBase
public virtual bool IsCompleted => Progress.Value >= 100 || Failed;
- public ContentDialogProgressViewModelBase Progress { get; } = new();
+ public ContentDialogProgressViewModelBase Progress { get; init; } = new();
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
index 0189e72ee..f23c8db00 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
@@ -10,6 +10,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
@@ -30,7 +31,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class CheckpointBrowserCardViewModel : ProgressViewModel
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -212,6 +213,12 @@ private void ToggleFavorite()
IsFavorite = settingsManager.Settings.FavoriteModels.Contains(CivitModel.Id);
}
+ [RelayCommand]
+ public void SearchAuthor()
+ {
+ EventManager.Instance.OnNavigateAndFindCivitAuthorRequested(CivitModel.Creator.Username);
+ }
+
[RelayCommand]
private async Task ShowVersionDialog(CivitModel model)
{
@@ -236,15 +243,16 @@ private async Task ShowVersionDialog(CivitModel model)
IsFooterVisible = false,
CloseOnClickOutside = true,
MaxDialogWidth = 750,
- MaxDialogHeight = 950,
+ MaxDialogHeight = 1000,
};
- var prunedDescription = Utilities.RemoveHtml(model.Description);
+ var htmlDescription = $"""{model.Description}""";
var viewModel = dialogFactory.Get();
viewModel.Dialog = dialog;
viewModel.Title = model.Name;
- viewModel.Description = prunedDescription;
+
+ viewModel.Description = htmlDescription;
viewModel.CivitModel = model;
viewModel.Versions = versions
.Where(v => !settingsManager.Settings.HideEarlyAccessModels || !v.IsEarlyAccess)
@@ -284,7 +292,8 @@ private async Task ShowVersionDialog(CivitModel model)
var defaultPath = Path.Combine(@"Models", sharedFolder);
var subFolder = viewModel?.SelectedInstallLocation ?? defaultPath;
- downloadPath = Path.Combine(settingsManager.LibraryDir, subFolder);
+ subFolder = subFolder.StripStart(@$"Models{Path.DirectorySeparatorChar}");
+ downloadPath = Path.Combine(settingsManager.ModelsDirectory, subFolder);
}
await Task.Delay(100);
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
index d0126e7a7..3f11591a0 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
@@ -15,6 +15,7 @@
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
+using Injectio.Attributes;
using NLog;
using Refit;
using StabilityMatrix.Avalonia.Languages;
@@ -36,7 +37,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
[View(typeof(CivitAiBrowserPage))]
-[Singleton]
+[RegisterSingleton]
public sealed partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScroll
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -208,6 +209,17 @@ or nameof(HideEarlyAccessModels)
settings => settings.HideEarlyAccessModels,
true
);
+
+ EventManager.Instance.NavigateAndFindCivitAuthorRequested += OnNavigateAndFindCivitAuthorRequested;
+ }
+
+ private void OnNavigateAndFindCivitAuthorRequested(object? sender, string e)
+ {
+ if (string.IsNullOrWhiteSpace(e))
+ return;
+
+ SearchQuery = $"@{e}";
+ SearchModelsCommand.ExecuteAsync(false).SafeFireAndForget();
}
private void OnNavigateAndFindCivitModelRequested(object? sender, int e)
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs
index 58c0e86eb..d502aeec8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs
@@ -15,6 +15,7 @@
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models.HuggingFace;
using StabilityMatrix.Avalonia.Services;
@@ -30,7 +31,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
[View(typeof(Views.HuggingFacePage))]
-[Singleton]
+[RegisterSingleton]
public partial class HuggingFacePageViewModel : TabViewModelBase
{
private readonly ITrackedDownloadService trackedDownloadService;
@@ -172,7 +173,7 @@ private async Task ImportSelected()
viewModel.NotifyExistsChanged();
}
};
- download.Start();
+ await trackedDownloadService.TryStartDownload(download);
await Task.Delay(Random.Shared.Next(50, 100));
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
index 420f32329..198dabe09 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
@@ -16,7 +17,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(CheckpointBrowserPage))]
-[Singleton]
+[RegisterSingleton]
public partial class CheckpointBrowserViewModel : PageViewModelBase
{
public override string Title => Resources.Label_ModelBrowser;
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs
index 453a93da4..ce61361b2 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs
@@ -50,6 +50,12 @@ public partial class CheckpointFileViewModel : SelectableViewModelBase
[ObservableProperty]
private bool hideImage;
+ [ObservableProperty]
+ private DateTimeOffset lastModified;
+
+ [ObservableProperty]
+ private DateTimeOffset created;
+
private readonly ISettingsManager settingsManager;
private readonly IModelIndexService modelIndexService;
private readonly INotificationService notificationService;
@@ -93,6 +99,8 @@ LocalModelFile checkpointFile
);
FileSize = GetFileSize(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory));
+ LastModified = GetLastModified(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory));
+ Created = GetCreated(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory));
}
[RelayCommand]
@@ -444,6 +452,24 @@ private long GetFileSize(string filePath)
return fileInfo.Length;
}
+ private DateTimeOffset GetLastModified(string filePath)
+ {
+ if (!File.Exists(filePath))
+ return DateTimeOffset.MinValue;
+
+ var fileInfo = new FileInfo(filePath);
+ return fileInfo.LastWriteTime;
+ }
+
+ private DateTimeOffset GetCreated(string filePath)
+ {
+ if (!File.Exists(filePath))
+ return DateTimeOffset.MinValue;
+
+ var fileInfo = new FileInfo(filePath);
+ return fileInfo.CreationTime;
+ }
+
private void UpdateImage()
{
if (
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
index 1083f9a5c..360b73099 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
@@ -18,6 +18,7 @@
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
@@ -48,7 +49,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(CheckpointsPage))]
-[Singleton]
+[RegisterSingleton]
public partial class CheckpointsPageViewModel(
ILogger logger,
ISettingsManager settingsManager,
@@ -279,6 +280,18 @@ or nameof(SortConnectedModelsFirst)
? comparer.ThenByAscending(vm => vm.FileSize)
: comparer.ThenByDescending(vm => vm.FileSize);
break;
+ case CheckpointSortMode.Created:
+ comparer =
+ SelectedSortDirection == ListSortDirection.Ascending
+ ? comparer.ThenByAscending(vm => vm.Created)
+ : comparer.ThenByDescending(vm => vm.Created);
+ break;
+ case CheckpointSortMode.LastModified:
+ comparer =
+ SelectedSortDirection == ListSortDirection.Ascending
+ ? comparer.ThenByAscending(vm => vm.LastModified)
+ : comparer.ThenByDescending(vm => vm.LastModified);
+ break;
default:
throw new ArgumentOutOfRangeException();
}
@@ -583,15 +596,15 @@ private async Task ShowVersionDialog(CheckpointFileViewModel item)
IsFooterVisible = false,
CloseOnClickOutside = true,
MaxDialogWidth = 750,
- MaxDialogHeight = 950,
+ MaxDialogHeight = 1000,
};
- var prunedDescription = Utilities.RemoveHtml(model.Description);
+ var htmlDescription = $"""{model.Description}""";
var viewModel = dialogFactory.Get();
viewModel.Dialog = dialog;
viewModel.Title = model.Name;
- viewModel.Description = prunedDescription;
+ viewModel.Description = htmlDescription;
viewModel.CivitModel = model;
viewModel.Versions = versions
.Select(version => new ModelVersionViewModel(modelIndexService, version))
@@ -621,7 +634,8 @@ private async Task ShowVersionDialog(CheckpointFileViewModel item)
var subFolder =
viewModel?.SelectedInstallLocation
?? Path.Combine(@"Models", model.Type.ConvertTo().GetStringValue());
- downloadPath = Path.Combine(settingsManager.LibraryDir, subFolder);
+ subFolder = subFolder.StripStart(@$"Models{Path.DirectorySeparatorChar}");
+ downloadPath = Path.Combine(settingsManager.ModelsDirectory, subFolder);
}
await Task.Delay(100);
diff --git a/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs
index 65a3bc1a0..e69b5ae71 100644
--- a/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs
@@ -1,12 +1,16 @@
using System;
+using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
+using System.Web;
using Avalonia.Threading;
+using AvaloniaEdit;
using AvaloniaEdit.Document;
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using Nito.AsyncEx;
using Nito.AsyncEx.Synchronous;
using NLog;
@@ -140,6 +144,34 @@ public async Task ResetWriteCursor()
DebugPrintDocument();
}
+ [RelayCommand]
+ private async Task CopySelection(TextEditor textEditor)
+ {
+ await App.Clipboard.SetTextAsync(textEditor.SelectedText);
+ }
+
+ [RelayCommand]
+ private void SelectAll(TextEditor textEditor)
+ {
+ textEditor.SelectAll();
+ }
+
+ [Localizable(false)]
+ [RelayCommand]
+ private void SearchWithGoogle(TextEditor textEditor)
+ {
+ var url = $"https://google.com/search?q={HttpUtility.UrlEncode(textEditor.SelectedText)}";
+ ProcessRunner.OpenUrl(url);
+ }
+
+ [Localizable(false)]
+ [RelayCommand]
+ private void SearchWithChatGpt(TextEditor textEditor)
+ {
+ var url = $"https://chatgpt.com/?q={HttpUtility.UrlEncode(textEditor.SelectedText)}";
+ ProcessRunner.OpenUrl(url);
+ }
+
private async Task ConsoleUpdateLoop()
{
// This must be run in the UI thread
diff --git a/StabilityMatrix.Avalonia/ViewModels/Controls/GitVersionSelectorViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Controls/GitVersionSelectorViewModel.cs
new file mode 100644
index 000000000..3a77ed8ee
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Controls/GitVersionSelectorViewModel.cs
@@ -0,0 +1,93 @@
+using System;
+using Avalonia.Data;
+using Avalonia.Layout;
+using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
+using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.Controls.Models;
+using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Core.Git;
+using StabilityMatrix.Core.Models;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Controls;
+
+public partial class GitVersionSelectorViewModel : ObservableObject
+{
+ [ObservableProperty]
+ private IGitVersionProvider? gitVersionProvider;
+
+ [ObservableProperty]
+ private string? selectedBranch;
+
+ [ObservableProperty]
+ private string? selectedCommit;
+
+ [ObservableProperty]
+ private string? selectedTag;
+
+ [ObservableProperty]
+ private GitVersionSelectorVersionType selectedVersionType;
+
+ ///
+ /// Gets or sets the selected .
+ ///
+ ///
+ public GitVersion SelectedGitVersion
+ {
+ get
+ {
+ return SelectedVersionType switch
+ {
+ GitVersionSelectorVersionType.BranchCommit
+ => new GitVersion { Branch = SelectedBranch, CommitSha = SelectedCommit },
+ GitVersionSelectorVersionType.Tag => new GitVersion { Tag = SelectedTag },
+ _ => throw new InvalidOperationException()
+ };
+ }
+ set
+ {
+ SelectedVersionType = value switch
+ {
+ { Tag: not null } => GitVersionSelectorVersionType.Tag,
+ { Branch: not null, CommitSha: not null } => GitVersionSelectorVersionType.BranchCommit,
+ // Default to branch commit
+ _ => GitVersionSelectorVersionType.BranchCommit
+ };
+
+ SelectedBranch = value.Branch;
+ SelectedCommit = value.CommitSha;
+ SelectedTag = value.Tag;
+ }
+ }
+
+ public BetterContentDialog GetDialog()
+ {
+ Dispatcher.UIThread.VerifyAccess();
+
+ var selector = new GitVersionSelector
+ {
+ DataContext = this,
+ Height = 400,
+ Width = 600,
+ [!GitVersionSelector.GitVersionProviderProperty] = new Binding(nameof(GitVersionProvider)),
+ [!GitVersionSelector.SelectedVersionTypeProperty] = new Binding(nameof(SelectedVersionType)),
+ [!GitVersionSelector.SelectedBranchProperty] = new Binding(nameof(SelectedBranch)),
+ [!GitVersionSelector.SelectedCommitProperty] = new Binding(nameof(SelectedCommit)),
+ [!GitVersionSelector.SelectedTagProperty] = new Binding(nameof(SelectedTag))
+ };
+
+ var dialog = new BetterContentDialog
+ {
+ Content = selector,
+ VerticalAlignment = VerticalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch,
+ PrimaryButtonText = Resources.Action_Save,
+ CloseButtonText = Resources.Action_Cancel,
+ DefaultButton = ContentDialogButton.Primary,
+ MinDialogWidth = 400
+ };
+
+ return dialog;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs
index b94afe7c0..d8b3937c7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs
@@ -1,16 +1,14 @@
-using System;
-using System.Collections.Concurrent;
+using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.ComponentModel;
-using System.Linq;
using System.Text.Json.Serialization;
using Avalonia.Media;
using Avalonia.Skia;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using SkiaSharp;
-using SoftCircuits.Collections;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Controls.Models;
using StabilityMatrix.Avalonia.Models;
@@ -24,7 +22,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Controls;
-[Transient]
+[RegisterTransient]
[ManagedService]
public partial class PaintCanvasViewModel(ILogger logger) : LoadableViewModelBase
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/AnalyticsOptInViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/AnalyticsOptInViewModel.cs
index b79a4998e..3f096c1e9 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/AnalyticsOptInViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/AnalyticsOptInViewModel.cs
@@ -1,4 +1,5 @@
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -9,11 +10,11 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(AnalyticsOptInDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public class AnalyticsOptInViewModel : ContentDialogViewModelBase
{
public string ChangeThisBehaviorInSettings =>
- string.Format(Resources.TextTemplate_YouCanChangeThisBehavior, "Settings | System | Analytics")
+ string.Format(Resources.TextTemplate_YouCanChangeThisBehavior, "Settings > System > Analytics")
.Trim();
public override BetterContentDialog GetDialog()
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmDeleteDialogViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmDeleteDialogViewModel.cs
index 523c6bfed..1de9ab412 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmDeleteDialogViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmDeleteDialogViewModel.cs
@@ -5,6 +5,7 @@
using Avalonia.Controls.Primitives;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
@@ -16,7 +17,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(ConfirmDeleteDialog))]
-[Transient]
+[RegisterTransient]
[ManagedService]
public partial class ConfirmDeleteDialogViewModel(ILogger logger)
: ContentDialogViewModelBase
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmPackageDeleteDialogViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmPackageDeleteDialogViewModel.cs
index 2d0ce30e1..de6cebb38 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmPackageDeleteDialogViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmPackageDeleteDialogViewModel.cs
@@ -1,5 +1,6 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
@@ -8,7 +9,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(ConfirmPackageDeleteDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ConfirmPackageDeleteDialogViewModel : ContentDialogViewModelBase
{
public required string ExpectedPackageName { get; set; }
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/DownloadResourceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/DownloadResourceViewModel.cs
index 2283b9d75..272c97772 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/DownloadResourceViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/DownloadResourceViewModel.cs
@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -20,7 +21,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(DownloadResourceDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class DownloadResourceViewModel(
IDownloadService downloadService,
ISettingsManager settingsManager,
@@ -90,7 +91,7 @@ Resource.ContextType as SharedFolderType?
download.ExtractRelativePath = Resource.ExtractRelativePath;
download.ContextAction = new ModelPostDownloadContextAction();
- download.Start();
+ trackedDownloadService.TryStartDownload(download);
EventManager.Instance.OnToggleProgressFlyout();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs
index cab1a11a0..da296f70d 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs
@@ -4,6 +4,7 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -13,7 +14,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(EnvVarsViewModel))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class EnvVarsViewModel : ContentDialogViewModelBase
{
[ObservableProperty]
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs
index f3accea42..650f2a021 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs
@@ -1,26 +1,115 @@
using System;
using System.ComponentModel;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
using System.Text;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
+using NLog;
using Sentry;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(ExceptionDialog))]
[ManagedService]
-[Transient]
-public class ExceptionViewModel : ViewModelBase
+[RegisterTransient]
+public partial class ExceptionViewModel : ViewModelBase
{
public Exception? Exception { get; set; }
public SentryId? SentryId { get; set; }
+ public bool IsRecoverable { get; set; }
+
+ public string Description =>
+ IsRecoverable
+ ? Resources.Text_UnexpectedErrorRecoverable_Description
+ : Resources.Text_UnexpectedError_Description;
+
public string? Message => Exception?.Message;
public string? ExceptionType => Exception?.GetType().Name ?? "";
+ public bool IsContinueResult { get; set; }
+
+ public string? LogZipPath { get; set; }
+
+ public static async Task CreateLogFolderZip()
+ {
+ var tcs = new TaskCompletionSource();
+ LogManager.Flush(
+ ex =>
+ {
+ if (ex is null)
+ {
+ tcs.SetResult();
+ }
+ else
+ {
+ tcs.SetException(ex);
+ }
+ },
+ TimeSpan.FromSeconds(15)
+ );
+ await tcs.Task;
+
+ using var suspend = LogManager.SuspendLogging();
+
+ var logDir = Compat.AppDataHome.JoinDir("Logs");
+
+ // Copy logs to temp directory
+ using var tempDir = new TempDirectoryPath();
+ var tempLogDir = tempDir.JoinDir("Logs");
+ tempLogDir.Create();
+ foreach (var logFile in logDir.EnumerateFiles("*.log"))
+ {
+ // Need FileShare.ReadWrite since NLog keeps the file open
+ await logFile.CopyToAsync(
+ tempLogDir.JoinFile(logFile.Name),
+ FileShare.ReadWrite,
+ overwrite: true
+ );
+ }
+
+ // Find a unique name for the output archive
+ var archiveNameBase = $"stabilitymatrix-log-{DateTime.Now:yyyy-MM-dd-HH-mm-ss}";
+ var archiveName = archiveNameBase;
+ var archivePath = Compat.AppDataHome.JoinFile(archiveName + ".zip");
+ var i = 1;
+ while (File.Exists(archivePath))
+ {
+ archiveName = $"{archiveNameBase}-{i++}";
+ archivePath = Compat.AppDataHome.JoinFile(archiveName + ".zip");
+ }
+
+ // Create the archive
+ ZipFile.CreateFromDirectory(tempLogDir, archivePath, CompressionLevel.Optimal, false);
+
+ return archivePath;
+ }
+
+ [RelayCommand]
+ private async Task OpenLogZipInFileBrowser()
+ {
+ if (string.IsNullOrWhiteSpace(LogZipPath) || !File.Exists(LogZipPath))
+ {
+ LogZipPath = await CreateLogFolderZip();
+ }
+
+ await ProcessRunner.OpenFileBrowser(LogZipPath);
+ }
+
[Localizable(false)]
public string? FormatAsMarkdown()
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ImageViewerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ImageViewerViewModel.cs
index fcce74a59..c8bfada22 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ImageViewerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ImageViewerViewModel.cs
@@ -5,6 +5,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.Core;
+using Injectio.Attributes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
@@ -26,7 +27,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(ImageViewerDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ImageViewerViewModel(
ILogger logger,
ISettingsManager settingsManager
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
index a9ceb1a94..76fff7199 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
@@ -7,6 +7,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -23,7 +24,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(InferenceConnectionHelpDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBase
{
private readonly ISettingsManager settingsManager;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/LaunchOptionsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/LaunchOptionsViewModel.cs
index 303d44620..ccdceb952 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/LaunchOptionsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/LaunchOptionsViewModel.cs
@@ -7,6 +7,7 @@
using System.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FuzzySharp;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
@@ -18,7 +19,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(LaunchOptionsDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class LaunchOptionsViewModel : ContentDialogViewModelBase
{
private readonly ILogger logger;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs
index 671a2eb4f..dec7c39aa 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs
@@ -6,6 +6,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using Refit;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
@@ -18,7 +19,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(LykosLoginDialog))]
-[Transient, ManagedService]
+[RegisterTransient, ManagedService]
public partial class LykosLoginViewModel(
IAccountsService accountsService,
ServiceManager vmFactory
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs
index 3be9f9d08..2dc5a436f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs
@@ -11,6 +11,7 @@
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using SkiaSharp;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Extensions;
@@ -25,7 +26,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
-[Transient]
+[RegisterTransient]
[ManagedService]
[View(typeof(MaskEditorDialog))]
public partial class MaskEditorViewModel(ServiceManager vmFactory)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelMetadataEditorDialogViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelMetadataEditorDialogViewModel.cs
index 7cada73d2..88564dea0 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelMetadataEditorDialogViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelMetadataEditorDialogViewModel.cs
@@ -5,6 +5,7 @@
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
@@ -19,7 +20,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(ModelMetadataEditorDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ModelMetadataEditorDialogViewModel(ISettingsManager settingsManager)
: ContentDialogViewModelBase,
IDropTarget
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
index f8da3be6a..9f31c3500 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
@@ -11,6 +11,7 @@
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models.PackageSteps;
@@ -29,7 +30,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
-[Transient]
+[RegisterTransient]
[ManagedService]
public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthConnectViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthConnectViewModel.cs
index db781c373..6e5488f93 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthConnectViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthConnectViewModel.cs
@@ -3,6 +3,7 @@
using System.Web;
using AsyncAwaitBestPractices;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using MessagePipe;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
@@ -16,7 +17,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(OAuthConnectDialog))]
-[Transient, ManagedService]
+[RegisterTransient, ManagedService]
public partial class OAuthConnectViewModel : ContentDialogViewModelBase
{
private readonly ILogger logger;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthGoogleLoginViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthGoogleLoginViewModel.cs
index b33f179fa..e73186a43 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthGoogleLoginViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthGoogleLoginViewModel.cs
@@ -6,6 +6,7 @@
using System.Web;
using Avalonia.Controls;
using DeviceId.Encoders;
+using Injectio.Attributes;
using MessagePipe;
using Microsoft.Extensions.Logging;
using NSec.Cryptography;
@@ -20,7 +21,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
-[Transient]
+[RegisterTransient]
[ManagedService]
[View(typeof(OAuthLoginDialog))]
public class OAuthGoogleLoginViewModel(
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthLoginViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthLoginViewModel.cs
index fa8a7a35f..9b35276b7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthLoginViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthLoginViewModel.cs
@@ -7,6 +7,7 @@
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using MessagePipe;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
@@ -24,7 +25,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
/// Like , but for handling full code responses from OAuth providers,
/// instead of being able to just close and refresh state.
///
-[Transient]
+[RegisterTransient]
[ManagedService]
[View(typeof(OAuthLoginDialog))]
public partial class OAuthLoginViewModel(
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
index 974a0d8a7..a5755b09b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
@@ -24,7 +25,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class OneClickInstallViewModel : ContentDialogViewModelBase
{
private readonly ISettingsManager settingsManager;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs
index d337b52c8..0cc851fdf 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs
@@ -6,6 +6,7 @@
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
@@ -21,7 +22,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(OpenArtWorkflowDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class OpenArtWorkflowViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs
index d069067ae..41389c23c 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs
@@ -9,6 +9,7 @@
using Avalonia.Controls;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
@@ -24,7 +25,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(PackageImportDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class PackageImportViewModel : ContentDialogViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PropertyGridViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PropertyGridViewModel.cs
index ba302e9a6..24f7c1093 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PropertyGridViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PropertyGridViewModel.cs
@@ -4,6 +4,7 @@
using Avalonia.PropertyGrid.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using OneOf;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
@@ -15,7 +16,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(PropertyGridDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class PropertyGridViewModel : ContentDialogViewModelBase
{
[ObservableProperty]
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackageSpecifiersViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackageSpecifiersViewModel.cs
index ea8d8e4b4..f14417741 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackageSpecifiersViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackageSpecifiersViewModel.cs
@@ -9,6 +9,7 @@
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -20,7 +21,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
-[Transient]
+[RegisterTransient]
[ManagedService]
[View(typeof(PythonPackageSpecifiersDialog))]
public partial class PythonPackageSpecifiersViewModel : ContentDialogViewModelBase
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs
index ca649e01e..c1bb261f1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs
@@ -14,6 +14,7 @@
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
@@ -32,7 +33,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(PythonPackagesDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
[AutoConstruct]
public partial class PythonPackagesViewModel : ContentDialogViewModelBase
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
index 97ada3ec4..a68a82812 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
@@ -9,6 +9,7 @@
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using Refit;
using StabilityMatrix.Avalonia.Services;
@@ -24,7 +25,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
-[Transient]
+[RegisterTransient]
[ManagedService]
public partial class RecommendedModelsViewModel : ContentDialogViewModelBase
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs
index c43b883ea..7e64756db 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs
@@ -8,6 +8,7 @@
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
@@ -20,7 +21,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(SelectDataDirectoryDialog))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
index 8f88fbdce..8aa2016d7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
@@ -10,6 +10,7 @@
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -26,7 +27,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class SelectModelVersionViewModel(
ISettingsManager settingsManager,
IDownloadService downloadService,
@@ -328,6 +329,15 @@ var directory in downloadDirectory.EnumerateDirectories(
}
}
+ if (downloadDirectory.ToString().EndsWith("Unet"))
+ {
+ // also add StableDiffusion in case we have an AIO version
+ var stableDiffusionDirectory = rootModelsDirectory.JoinDir(
+ SharedFolderType.StableDiffusion.GetStringValue()
+ );
+ installLocations.Add(stableDiffusionDirectory.ToString().Replace(rootModelsDirectory, "Models"));
+ }
+
installLocations.Add("Custom...");
AvailableInstallLocations = installLocations;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
index 888de3c5f..9091b5472 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
@@ -6,9 +6,12 @@
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using DynamicData;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using Semver;
using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
@@ -24,7 +27,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(UpdateDialog))]
[ManagedService]
-[Singleton]
+[RegisterSingleton]
public partial class UpdateViewModel : ContentDialogViewModelBase
{
private readonly ILogger logger;
@@ -177,10 +180,14 @@ await updateHelper.DownloadUpdate(
{
await Task.Run(() =>
{
- ProcessRunner.StartApp(
- UpdateHelper.ExecutablePath.FullPath,
- new[] { "--wait-for-exit-pid", $"{Environment.ProcessId}" }
- );
+ var args = new[] { "--wait-for-exit-pid", $"{Environment.ProcessId}" };
+
+ if (Program.Args.NoSentry)
+ {
+ args = args.Append("--no-sentry").ToArray();
+ }
+
+ ProcessRunner.StartApp(UpdateHelper.ExecutablePath.FullPath, args);
});
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
index 080c29fa4..e6d3f54eb 100644
--- a/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
@@ -1,8 +1,11 @@
using System;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Styles;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -10,13 +13,14 @@
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo;
+using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(FirstLaunchSetupWindow))]
[ManagedService]
-[Singleton]
-public partial class FirstLaunchSetupViewModel : ViewModelBase
+[RegisterSingleton]
+public partial class FirstLaunchSetupViewModel : DisposableViewModelBase
{
[ObservableProperty]
private bool eulaAccepted;
@@ -34,9 +38,33 @@ public partial class FirstLaunchSetupViewModel : ViewModelBase
FailColorBrush = ThemeColors.ThemeYellow,
};
- public FirstLaunchSetupViewModel()
+ [ObservableProperty]
+ private bool selectDifferentGpu;
+
+ [ObservableProperty]
+ private ObservableCollection gpuInfoCollection = [];
+
+ [ObservableProperty]
+ private GpuInfo? selectedGpu;
+
+ public string YouCanChangeThis =>
+ string.Format(
+ Resources.TextTemplate_YouCanChangeThisBehavior,
+ "Settings > System Settings > Default GPU"
+ );
+
+ public FirstLaunchSetupViewModel(ISettingsManager settingsManager)
{
CheckHardwareBadge.RefreshFunc = SetGpuInfo;
+
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.SelectedGpu,
+ settings => settings.PreferredGpu,
+ true
+ )
+ );
}
private async Task SetGpuInfo()
@@ -47,6 +75,7 @@ private async Task SetGpuInfo()
{
// Query GPU info
gpuInfo = await Task.Run(() => HardwareHelper.IterGpuInfo().ToArray());
+ GpuInfoCollection = new ObservableCollection(gpuInfo);
}
// First Nvidia GPU
@@ -58,6 +87,7 @@ private async Task SetGpuInfo()
// Otherwise first GPU
activeGpu ??= gpuInfo.FirstOrDefault();
+ SelectedGpu = activeGpu;
GpuInfoText = activeGpu is null
? "No GPU detected"
: $"{activeGpu.Name} ({Size.FormatBytes(activeGpu.MemoryBytes)})";
@@ -76,4 +106,10 @@ public override void OnLoaded()
base.OnLoaded();
CheckHardwareBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();
}
+
+ [RelayCommand]
+ private void ToggleManualGpu()
+ {
+ SelectDifferentGpu = !SelectDifferentGpu;
+ }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/BatchSizeCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/BatchSizeCardViewModel.cs
index 2e7d525d4..ec52a2f64 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/BatchSizeCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/BatchSizeCardViewModel.cs
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -10,7 +11,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(BatchSizeCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class BatchSizeCardViewModel : LoadableViewModelBase, IComfyStep
{
[NotifyDataErrorInfo]
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs
index 41eaa811c..53bb8036e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs
@@ -7,6 +7,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData.Binding;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -20,7 +21,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(ControlNetCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ControlNetCardViewModel : LoadableViewModelBase
{
public const string ModuleKey = "ControlNet";
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/DiscreteModelSamplingCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/DiscreteModelSamplingCardViewModel.cs
new file mode 100644
index 000000000..5428ea2f2
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/DiscreteModelSamplingCardViewModel.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Core.Attributes;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference;
+
+[View(typeof(DiscreteModelSamplingCard))]
+[ManagedService]
+[RegisterTransient]
+public partial class DiscreteModelSamplingCardViewModel : LoadableViewModelBase
+{
+ public const string ModuleKey = "DiscreteModelSampling";
+ public List SamplingMethods => ["eps", "v_prediction", "lcm", "x0"];
+
+ [ObservableProperty]
+ private bool isZsnrEnabled;
+
+ [ObservableProperty]
+ private string selectedSamplingMethod = "eps";
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs
index 349769168..2f24f6d98 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs
@@ -5,6 +5,7 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -16,7 +17,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(ExtraNetworkCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ExtraNetworkCardViewModel(IInferenceClientManager clientManager) : LoadableViewModelBase
{
public const string ModuleKey = "ExtraNetwork";
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs
index fae5f1932..4eba0fbfc 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs
@@ -1,6 +1,7 @@
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -12,7 +13,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(FaceDetailerCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class FaceDetailerViewModel : LoadableViewModelBase
{
private readonly ServiceManager vmFactory;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs
index 1aac1239e..534aaf37b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -8,7 +9,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(FreeUCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class FreeUCardViewModel : LoadableViewModelBase
{
public const string ModuleKey = "FreeU";
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs
index 84dc2fc51..b387c7f84 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs
@@ -14,6 +14,7 @@
using DynamicData.Binding;
using FuzzySharp;
using FuzzySharp.PreProcess;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using SkiaSharp;
using StabilityMatrix.Avalonia.Controls;
@@ -36,7 +37,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(ImageFolderCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ImageFolderCardViewModel : DisposableViewModelBase
{
private readonly ILogger logger;
@@ -379,6 +380,61 @@ await Task.Run(() =>
notificationService.Show("Image Exported", $"Saved to {targetPath}", NotificationType.Success);
}
+ [RelayCommand]
+ private async Task CopySeedToClipboard(LocalImageFile? item)
+ {
+ if (item?.GenerationParameters is null || App.Clipboard is null)
+ {
+ return;
+ }
+
+ await App.Clipboard.SetTextAsync(item.GenerationParameters.Seed.ToString());
+ }
+
+ [RelayCommand]
+ private async Task CopyPromptToClipboard(LocalImageFile? item)
+ {
+ if (item?.GenerationParameters is null || App.Clipboard is null)
+ {
+ return;
+ }
+
+ await App.Clipboard.SetTextAsync(item.GenerationParameters.PositivePrompt);
+ }
+
+ [RelayCommand]
+ private async Task CopyNegativePromptToClipboard(LocalImageFile? item)
+ {
+ if (item?.GenerationParameters is null || App.Clipboard is null)
+ {
+ return;
+ }
+
+ await App.Clipboard.SetTextAsync(item.GenerationParameters.NegativePrompt);
+ }
+
+ [RelayCommand]
+ private async Task CopyModelNameToClipboard(LocalImageFile? item)
+ {
+ if (item?.GenerationParameters is null || App.Clipboard is null)
+ {
+ return;
+ }
+
+ await App.Clipboard.SetTextAsync(item.GenerationParameters.ModelName);
+ }
+
+ [RelayCommand]
+ private async Task CopyModelHashToClipboard(LocalImageFile? item)
+ {
+ if (item?.GenerationParameters is null || App.Clipboard is null)
+ {
+ return;
+ }
+
+ await App.Clipboard.SetTextAsync(item.GenerationParameters.ModelHash);
+ }
+
[RelayCommand]
private Task OnImageExportPng(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Png);
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs
index 93497d430..04189239a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs
@@ -8,6 +8,7 @@
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Helpers;
@@ -24,7 +25,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(ImageGalleryCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ImageGalleryCardViewModel : ViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -158,10 +159,7 @@ private async Task FlyoutPreview(IImage? image)
var viewerVm = vmFactory.Get();
viewerVm.ImageSource = new ImageSource((Bitmap)image);
- var dialog = new BetterContentDialog
- {
- Content = new ImageViewerDialog { DataContext = viewerVm, }
- };
+ var dialog = new BetterContentDialog { Content = new ImageViewerDialog { DataContext = viewerVm, } };
await dialog.ShowAsync();
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs
index 412101d1d..027d34a0a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs
@@ -5,6 +5,7 @@
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -22,7 +23,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceTextToImageView), IsPersistent = true)]
[ManagedService]
-[Transient]
+[RegisterTransient]
public class InferenceFluxTextToImageViewModel : InferenceGenerationViewModelBase, IParametersLoadableState
{
private readonly INotificationService notificationService;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs
index 554f1ee38..433c81d57 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -16,7 +17,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceImageToImageView), IsPersistent = true)]
-[Transient, ManagedService]
+[RegisterTransient, ManagedService]
public class InferenceImageToImageViewModel : InferenceTextToImageViewModel
{
[JsonPropertyName("SelectImage")]
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
index 4a0d7d34d..bae182e92 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
@@ -6,6 +6,7 @@
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -23,7 +24,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceImageToVideoView), persistent: true)]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class InferenceImageToVideoViewModel
: InferenceGenerationViewModelBase,
IParametersLoadableState
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
index 540de0d97..853dfcf19 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
@@ -4,6 +4,7 @@
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models;
@@ -24,7 +25,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceImageUpscaleView), persistent: true)]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
[ManagedService]
-[Transient]
+[RegisterTransient]
public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
index dc49664de..ecb2e2437 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using DynamicData.Binding;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models;
@@ -27,7 +28,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(InferenceTextToImageView), IsPersistent = true)]
[ManagedService]
-[Transient]
+[RegisterTransient]
public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, IParametersLoadableState
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs
index be1b275df..1d0524bd9 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using KGySoft.CoreLibraries;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -12,7 +13,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference;
-[Transient]
+[RegisterTransient]
[ManagedService]
[View(typeof(LayerDiffuseCard))]
public partial class LayerDiffuseCardViewModel : LoadableViewModelBase, IComfyStep
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
index 098fc60cd..b6d2e21b0 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs
@@ -7,7 +7,7 @@
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
-using DynamicData.Binding;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -25,7 +25,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(ModelCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class ModelCardViewModel(
IInferenceClientManager clientManager,
ServiceManager vmFactory
@@ -76,13 +76,24 @@ ServiceManager vmFactory
[ObservableProperty]
private HybridModelFile? selectedClip2;
+ [ObservableProperty]
+ private HybridModelFile? selectedClip3;
+
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(IsSd3Clip))]
+ private string? selectedClipType;
+
[ObservableProperty]
private string? selectedDType;
[ObservableProperty]
private bool enableModelLoaderSelection = true;
+ [ObservableProperty]
+ private bool isClipModelSelectionEnabled;
+
public List WeightDTypes { get; set; } = ["default", "fp8_e4m3fn", "fp8_e5m2"];
+ public List ClipTypes { get; set; } = ["flux", "sd3"];
public StackEditableCardViewModel ExtraNetworksStackCardViewModel { get; } =
new(vmFactory) { Title = Resources.Label_ExtraNetworks, AvailableModules = [typeof(LoraModule)] };
@@ -93,6 +104,7 @@ ServiceManager vmFactory
public bool IsStandaloneModelLoader => SelectedModelLoader is ModelLoader.Unet or ModelLoader.Gguf;
public bool ShowPrecisionSelection => SelectedModelLoader is ModelLoader.Unet;
+ public bool IsSd3Clip => SelectedClipType == "sd3";
[RelayCommand]
private static async Task OnConfigClickAsync()
@@ -217,6 +229,9 @@ public override JsonObject SaveStateToJsonObject()
IsModelLoaderSelectionEnabled = IsModelLoaderSelectionEnabled,
SelectedClip1Name = SelectedClip1?.RelativePath,
SelectedClip2Name = SelectedClip2?.RelativePath,
+ SelectedClip3Name = SelectedClip3?.RelativePath,
+ SelectedClipType = SelectedClipType,
+ IsClipModelSelectionEnabled = IsClipModelSelectionEnabled,
ModelLoader = SelectedModelLoader,
ExtraNetworks = ExtraNetworksStackCardViewModel.SaveStateToJsonObject()
}
@@ -259,6 +274,12 @@ public override void LoadStateFromJsonObject(JsonObject state)
? HybridModelFile.None
: ClientManager.ClipModels.FirstOrDefault(x => x.RelativePath == model.SelectedClip2Name);
+ SelectedClip3 = model.SelectedClip3Name is null
+ ? HybridModelFile.None
+ : ClientManager.ClipModels.FirstOrDefault(x => x.RelativePath == model.SelectedClip3Name);
+
+ SelectedClipType = model.SelectedClipType;
+
ClipSkip = model.ClipSkip;
IsVaeSelectionEnabled = model.IsVaeSelectionEnabled;
@@ -266,6 +287,7 @@ public override void LoadStateFromJsonObject(JsonObject state)
IsClipSkipEnabled = model.IsClipSkipEnabled;
IsExtraNetworksEnabled = model.IsExtraNetworksEnabled;
IsModelLoaderSelectionEnabled = model.IsModelLoaderSelectionEnabled;
+ IsClipModelSelectionEnabled = model.IsClipModelSelectionEnabled;
if (model.ExtraNetworks is not null)
{
@@ -333,9 +355,13 @@ public GenerationParameters SaveStateToParameters(GenerationParameters parameter
partial void OnSelectedModelLoaderChanged(ModelLoader value)
{
- if (value is ModelLoader.Unet or ModelLoader.Gguf && IsVaeSelectionEnabled is false)
+ if (value is ModelLoader.Unet or ModelLoader.Gguf)
{
- IsVaeSelectionEnabled = true;
+ if (!IsVaeSelectionEnabled)
+ IsVaeSelectionEnabled = true;
+
+ if (!IsClipModelSelectionEnabled)
+ IsClipModelSelectionEnabled = true;
}
}
@@ -377,17 +403,26 @@ private void SetupStandaloneModelLoader(ModuleApplyStepEventArgs e)
);
e.Builder.Connections.Base.VAE = vaeLoader.Output;
- // DualCLIPLoader
- var clipLoader = e.Nodes.AddTypedNode(
- new ComfyNodeBuilder.DualCLIPLoader
- {
- Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.DualCLIPLoader)),
- ClipName1 = SelectedClip1?.RelativePath ?? throw new ValidationException("No Clip1 Selected"),
- ClipName2 = SelectedClip2?.RelativePath ?? throw new ValidationException("No Clip2 Selected"),
- Type = "flux"
- }
- );
- e.Builder.Connections.Base.Clip = clipLoader.Output;
+ if (SelectedClipType == "flux")
+ {
+ // DualCLIPLoader
+ var clipLoader = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.DualCLIPLoader
+ {
+ Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.DualCLIPLoader)),
+ ClipName1 =
+ SelectedClip1?.RelativePath ?? throw new ValidationException("No Clip1 Selected"),
+ ClipName2 =
+ SelectedClip2?.RelativePath ?? throw new ValidationException("No Clip2 Selected"),
+ Type = SelectedClipType ?? throw new ValidationException("No Clip Type Selected")
+ }
+ );
+ e.Builder.Connections.Base.Clip = clipLoader.Output;
+ }
+ else
+ {
+ SetupClipLoaders(e);
+ }
}
private void SetupDefaultModelLoader(ModuleApplyStepEventArgs e)
@@ -410,9 +445,17 @@ SelectedModelLoader is ModelLoader.Default
var baseLoader = e.Nodes.AddTypedNode(loaderNode);
e.Builder.Connections.Base.Model = baseLoader.Output1;
- e.Builder.Connections.Base.Clip = baseLoader.Output2;
e.Builder.Connections.Base.VAE = baseLoader.Output3;
+ if (IsClipModelSelectionEnabled)
+ {
+ SetupClipLoaders(e);
+ }
+ else
+ {
+ e.Builder.Connections.Base.Clip = baseLoader.Output2;
+ }
+
// Load refiner if enabled
if (IsRefinerSelectionEnabled && SelectedRefiner is { IsNone: false })
{
@@ -446,6 +489,58 @@ SelectedModelLoader is ModelLoader.Default
}
}
+ private void SetupClipLoaders(ModuleApplyStepEventArgs e)
+ {
+ if (
+ SelectedClip3 is { IsNone: false }
+ && SelectedClip2 is { IsNone: false }
+ && SelectedClip1 is { IsNone: false }
+ )
+ {
+ var clipLoader = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.TripleCLIPLoader
+ {
+ Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.TripleCLIPLoader)),
+ ClipName1 =
+ SelectedClip1?.RelativePath ?? throw new ValidationException("No Clip1 Selected"),
+ ClipName2 =
+ SelectedClip2?.RelativePath ?? throw new ValidationException("No Clip2 Selected"),
+ ClipName3 =
+ SelectedClip3?.RelativePath ?? throw new ValidationException("No Clip3 Selected")
+ }
+ );
+ e.Builder.Connections.Base.Clip = clipLoader.Output;
+ }
+ else if (SelectedClip2 is { IsNone: false } && SelectedClip1 is { IsNone: false })
+ {
+ var clipLoader = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.DualCLIPLoader
+ {
+ Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.DualCLIPLoader)),
+ ClipName1 =
+ SelectedClip1?.RelativePath ?? throw new ValidationException("No Clip1 Selected"),
+ ClipName2 =
+ SelectedClip2?.RelativePath ?? throw new ValidationException("No Clip2 Selected"),
+ Type = SelectedClipType ?? throw new ValidationException("No Clip Type Selected")
+ }
+ );
+ e.Builder.Connections.Base.Clip = clipLoader.Output;
+ }
+ else if (SelectedClip1 is { IsNone: false })
+ {
+ var clipLoader = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.CLIPLoader()
+ {
+ Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CLIPLoader)),
+ ClipName =
+ SelectedClip1?.RelativePath ?? throw new ValidationException("No Clip1 Selected"),
+ Type = SelectedClipType ?? throw new ValidationException("No Clip Type Selected")
+ }
+ );
+ e.Builder.Connections.Base.Clip = clipLoader.Output;
+ }
+ }
+
internal class ModelCardModel
{
public string? SelectedModelName { get; init; }
@@ -453,6 +548,8 @@ internal class ModelCardModel
public string? SelectedVaeName { get; init; }
public string? SelectedClip1Name { get; init; }
public string? SelectedClip2Name { get; init; }
+ public string? SelectedClip3Name { get; init; }
+ public string? SelectedClipType { get; init; }
public ModelLoader ModelLoader { get; init; }
public int ClipSkip { get; init; } = 1;
@@ -461,6 +558,7 @@ internal class ModelCardModel
public bool IsClipSkipEnabled { get; init; }
public bool IsExtraNetworksEnabled { get; init; }
public bool IsModelLoaderSelectionEnabled { get; init; }
+ public bool IsClipModelSelectionEnabled { get; init; }
public JsonObject? ExtraNetworks { get; init; }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
index 779551920..9b7780c7a 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -14,7 +15,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class ControlNetModule : ModuleBase
{
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs
new file mode 100644
index 000000000..4a80f5ed1
--- /dev/null
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs
@@ -0,0 +1,44 @@
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.Services;
+using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+
+namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
+
+[ManagedService]
+[RegisterTransient]
+public class DiscreteModelSamplingModule : ModuleBase
+{
+ public DiscreteModelSamplingModule(ServiceManager vmFactory)
+ : base(vmFactory)
+ {
+ Title = "Discrete Model Sampling";
+ AddCards(vmFactory.Get());
+ }
+
+ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
+ {
+ var vm = GetCard();
+
+ foreach (var modelConnections in e.Builder.Connections.Models.Values)
+ {
+ if (modelConnections.Model is not { } model)
+ continue;
+
+ var modelSamplingDiscrete = e.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.ModelSamplingDiscrete
+ {
+ Name = e.Nodes.GetUniqueName("ModelSamplingDiscrete"),
+ Model = model,
+ Sampling = vm.SelectedSamplingMethod,
+ Zsnr = vm.IsZsnrEnabled
+ }
+ );
+
+ modelConnections.Model = modelSamplingDiscrete.Output;
+ e.Temp.Base.Model = modelSamplingDiscrete.Output;
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs
index 64130bd0f..b5edad480 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -12,7 +13,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class FaceDetailerModule : ModuleBase, IValidatableModule
{
public FaceDetailerModule(ServiceManager vmFactory)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs
index 83f5973f5..d74bacc5c 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs
@@ -1,4 +1,5 @@
-using StabilityMatrix.Avalonia.Models.Inference;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -6,7 +7,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class FluxGuidanceModule : ModuleBase
{
public FluxGuidanceModule(ServiceManager vmFactory)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs
index 6472ae07b..3c3a0a63f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs
@@ -1,4 +1,5 @@
-using StabilityMatrix.Avalonia.Models.Inference;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -9,7 +10,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class FluxHiresFixModule(ServiceManager vmFactory) : HiresFixModule(vmFactory)
{
protected override void OnApplyStep(ModuleApplyStepEventArgs e)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs
index 67ab39396..1e86ecd61 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -8,7 +9,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class FreeUModule : ModuleBase
{
///
@@ -31,7 +32,7 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
foreach (var modelConnections in e.Builder.Connections.Models.Values.Where(m => m.Model is not null))
{
- modelConnections.Model = e.Nodes.AddTypedNode(
+ var freeUOutput = e.Nodes.AddTypedNode(
new ComfyNodeBuilder.FreeU
{
Name = e.Nodes.GetUniqueName($"FreeU_{modelConnections.Name}"),
@@ -42,6 +43,9 @@ protected override void OnApplyStep(ModuleApplyStepEventArgs e)
S2 = card.S2
}
).Output;
+
+ modelConnections.Model = freeUOutput;
+ e.Temp.Base.Model = freeUOutput;
}
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
index 94a0d1bcb..d20512d45 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -15,7 +16,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class HiresFixModule : ModuleBase
{
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs
index 11a701143..489095674 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs
@@ -1,4 +1,5 @@
-using StabilityMatrix.Avalonia.Models.Inference;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -6,7 +7,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class LayerDiffuseModule : ModuleBase
{
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs
index cd2df337b..712e17a87 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs
@@ -1,6 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -12,7 +13,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class LoraModule : ModuleBase
{
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs
index 258f69059..5c998fde1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs
@@ -1,4 +1,5 @@
using System;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -8,7 +9,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class PromptExpansionModule : ModuleBase
{
public PromptExpansionModule(ServiceManager vmFactory)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs
index cfe39389b..934e475a4 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs
@@ -1,4 +1,5 @@
-using StabilityMatrix.Avalonia.Languages;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -8,7 +9,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class SaveImageModule : ModuleBase
{
///
@@ -21,15 +22,13 @@ public SaveImageModule(ServiceManager vmFactory)
///
protected override void OnApplyStep(ModuleApplyStepEventArgs e)
{
- var preview = e.Builder
- .Nodes
- .AddTypedNode(
- new ComfyNodeBuilder.PreviewImage
- {
- Name = e.Builder.Nodes.GetUniqueName("SaveIntermediateImage"),
- Images = e.Builder.GetPrimaryAsImage()
- }
- );
+ var preview = e.Builder.Nodes.AddTypedNode(
+ new ComfyNodeBuilder.PreviewImage
+ {
+ Name = e.Builder.Nodes.GetUniqueName("SaveIntermediateImage"),
+ Images = e.Builder.GetPrimaryAsImage()
+ }
+ );
e.Builder.Connections.OutputNodes.Add(preview);
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs
index 185cceab6..7639e33cb 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -9,7 +10,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
[ManagedService]
-[Transient]
+[RegisterTransient]
public class UpscalerModule : ModuleBase
{
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
index f956a5444..506b68e74 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs
@@ -7,6 +7,7 @@
using AvaloniaEdit.Document;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -27,7 +28,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(PromptCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class PromptCardViewModel
: DisposableLoadableViewModelBase,
IParametersLoadableState,
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptExpansionCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptExpansionCardViewModel.cs
index 1c615e148..ceb26c02f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptExpansionCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptExpansionCardViewModel.cs
@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -9,7 +10,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(PromptExpansionCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class PromptExpansionCardViewModel(IInferenceClientManager clientManager)
: LoadableViewModelBase
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
index 858923220..c7f6bd0bc 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
@@ -4,6 +4,7 @@
using System.Linq;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -26,7 +27,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(SamplerCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLoadableState, IComfyStep
{
public const string ModuleKey = "Sampler";
@@ -118,7 +119,8 @@ ServiceManager vmFactory
typeof(FreeUModule),
typeof(ControlNetModule),
typeof(LayerDiffuseModule),
- typeof(FluxGuidanceModule)
+ typeof(FluxGuidanceModule),
+ typeof(DiscreteModelSamplingModule)
];
});
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SeedCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SeedCardViewModel.cs
index 1f0a9fa4d..0fccdb027 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/SeedCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SeedCardViewModel.cs
@@ -2,6 +2,7 @@
using System.Text.Json.Nodes;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -11,7 +12,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(SeedCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class SeedCardViewModel : LoadableViewModelBase
{
[ObservableProperty, NotifyPropertyChangedFor(nameof(RandomizeButtonToolTip))]
@@ -41,8 +42,6 @@ public override void LoadStateFromJsonObject(JsonObject state)
///
public override JsonObject SaveStateToJsonObject()
{
- return SerializeModel(
- new SeedCardModel { Seed = Seed, IsRandomizeEnabled = IsRandomizeEnabled }
- );
+ return SerializeModel(new SeedCardModel { Seed = Seed, IsRandomizeEnabled = IsRandomizeEnabled });
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs
index 05fe26523..5e7233f29 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs
@@ -13,6 +13,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Extensions;
@@ -31,7 +32,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(SelectImageCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class SelectImageCardViewModel(
INotificationService notificationService,
ServiceManager vmFactory
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SharpenCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SharpenCardViewModel.cs
index 90a67a121..dfb66d643 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/SharpenCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SharpenCardViewModel.cs
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@@ -8,7 +9,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(SharpenCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class SharpenCardViewModel : LoadableViewModelBase
{
[Range(1, 31)]
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs
index 1c8b7293c..04c2ca198 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs
@@ -1,5 +1,6 @@
using System.Linq;
using System.Text.Json.Nodes;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -11,7 +12,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(StackCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public class StackCardViewModel : StackViewModelBase
{
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs
index c053666c1..63ead44c7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs
@@ -4,6 +4,7 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -16,7 +17,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(StackEditableCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class StackEditableCardViewModel : StackViewModelBase, IComfyStep
{
private readonly ServiceManager vmFactory;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs
index c188dbb95..ffeaf7050 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs
@@ -2,6 +2,7 @@
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -12,7 +13,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(StackExpander))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class StackExpanderViewModel : StackViewModelBase
{
public const string ModuleKey = "StackExpander";
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/UnetModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/UnetModelCardViewModel.cs
index 22f280fd1..b9f7e40cd 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/UnetModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/UnetModelCardViewModel.cs
@@ -5,6 +5,7 @@
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -19,7 +20,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(UnetModelCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class UnetModelCardViewModel(IInferenceClientManager clientManager)
: LoadableViewModelBase,
IParametersLoadableState,
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs
index 932992943..23bb63cf4 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs
@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -11,7 +12,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference;
[View(typeof(UpscalerCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class UpscalerCardViewModel : LoadableViewModelBase
{
public const string ModuleKey = "Upscaler";
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
index 01ef38c4a..b8f3f81a9 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.Services;
@@ -10,7 +11,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;
[View(typeof(ModelCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public class ImgToVidModelCardViewModel : ModelCardViewModel
{
public ImgToVidModelCardViewModel(
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
index 1bb70b269..d90aa73e3 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -13,7 +14,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;
[View(typeof(VideoGenerationSettingsCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class SvdImgToVidConditioningViewModel
: LoadableViewModelBase,
IParametersLoadableState,
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
index d44f469ae..f2aebe9e8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -14,7 +15,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;
[View(typeof(VideoOutputSettingsCard))]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class VideoOutputSettingsCardViewModel
: LoadableViewModelBase,
IParametersLoadableState,
diff --git a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs
index 07d606892..3436078c5 100644
--- a/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs
@@ -17,6 +17,7 @@
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
@@ -46,7 +47,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[Preload]
[View(typeof(InferencePage))]
-[Singleton]
+[RegisterSingleton]
public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
index 0cd1a8564..c80c3699f 100644
--- a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
@@ -1,18 +1,22 @@
using System;
using System.IO;
using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using System.Reactive.Linq;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
+using DynamicData.Alias;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
+using KGySoft.CoreLibraries;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -28,7 +32,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(InstalledWorkflowsPage))]
-[Singleton]
+[RegisterSingleton]
public partial class InstalledWorkflowsViewModel(
ISettingsManager settingsManager,
INotificationService notificationService
@@ -43,11 +47,26 @@ INotificationService notificationService
private IObservableCollection displayedWorkflows =
new ObservableCollectionExtended();
+ [ObservableProperty]
+ private string searchQuery = string.Empty;
+
protected override async Task OnInitialLoadedAsync()
{
await base.OnInitialLoadedAsync();
- workflowsCache.Connect().DeferUntilLoaded().Bind(DisplayedWorkflows).ObserveOn(SynchronizationContext.Current).Subscribe();
+ var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
+ .Throttle(TimeSpan.FromMilliseconds(100))
+ .DistinctUntilChanged()
+ .Select(_ => (Func)FilterWorkflows);
+
+ workflowsCache
+ .Connect()
+ .DeferUntilLoaded()
+ .Filter(searchPredicate)
+ .SortBy(x => x.Index)
+ .Bind(DisplayedWorkflows)
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe();
if (Design.IsDesignMode)
return;
@@ -66,6 +85,8 @@ private async Task LoadInstalledWorkflowsAsync()
Directory.CreateDirectory(settingsManager.WorkflowDirectory);
}
+ var count = 0;
+
foreach (
var workflowPath in Directory.EnumerateFiles(
settingsManager.WorkflowDirectory,
@@ -86,8 +107,9 @@ var workflowPath in Directory.EnumerateFiles(
Workflow = new OpenArtSearchResult
{
Id = Guid.NewGuid().ToString(),
- Name = Path.GetFileNameWithoutExtension(workflowPath)
- }
+ Name = Path.GetFileNameWithoutExtension(workflowPath),
+ },
+ Index = count++,
};
}
@@ -162,6 +184,20 @@ await notificationService.TryAsync(
);
}
+ private bool FilterWorkflows(OpenArtMetadata metadata)
+ {
+ if (string.IsNullOrWhiteSpace(SearchQuery))
+ return true;
+
+ if (metadata.HasMetadata)
+ {
+ return metadata.Workflow.Creator.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase)
+ || metadata.Workflow.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase);
+ }
+
+ return metadata.Workflow?.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ?? false;
+ }
+
private void OnWorkflowInstalled(object? sender, EventArgs e)
{
LoadInstalledWorkflowsAsync().SafeFireAndForget();
diff --git a/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
index 0897932a6..7c52302e9 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
@@ -12,6 +12,7 @@
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using Refit;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
@@ -32,7 +33,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(OpenArtBrowserPage))]
-[Singleton]
+[RegisterSingleton]
public partial class OpenArtBrowserViewModel(
IOpenArtApi openArtApi,
INotificationService notificationService,
diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
index f53c8844f..e30b6c2d8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
@@ -18,6 +18,7 @@
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using Nito.Disposables.Internals;
using StabilityMatrix.Avalonia.Controls;
@@ -46,7 +47,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(Views.OutputsPage))]
-[Singleton]
+[RegisterSingleton]
public partial class OutputsPageViewModel : PageViewModelBase
{
private readonly ISettingsManager settingsManager;
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
index 65dd3b763..67b98c1d6 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
@@ -14,6 +14,7 @@
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Languages;
@@ -36,7 +37,8 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
///
[View(typeof(MainPackageManagerView))]
-[Singleton, ManagedService]
+[ManagedService]
+[RegisterSingleton]
public partial class MainPackageManagerViewModel : PageViewModelBase
{
private readonly ISettingsManager settingsManager;
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
index 3632ff371..8d3975574 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
@@ -14,6 +14,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Controls;
@@ -37,7 +38,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class PackageCardViewModel(
ILogger logger,
IPackageFactory packageFactory,
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs
index 3a6db87bc..3c0b30db7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs
@@ -1,46 +1,55 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.Contracts;
+using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
+using Avalonia.Controls.Notifications;
+using Avalonia.Data;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Collections;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.ViewModels.Controls;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.Views.PackageManager;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Git;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages.Extensions;
+using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
[View(typeof(PackageExtensionBrowserView))]
-[Transient]
+[RegisterTransient]
[ManagedService]
public partial class PackageExtensionBrowserViewModel : ViewModelBase, IDisposable
{
private readonly INotificationService notificationService;
private readonly ISettingsManager settingsManager;
private readonly ServiceManager vmFactory;
+ private readonly IPrerequisiteHelper prerequisiteHelper;
private readonly CompositeDisposable cleanUp;
public PackagePair? PackagePair { get; set; }
@@ -76,21 +85,45 @@ public SearchCollection<
string
> InstalledItemsSearchCollection { get; }
- public IObservableCollection InstalledExtensions { get; } =
- new ObservableCollectionExtended();
+ private SourceCache extensionPackSource = new(ext => ext.Name);
+
+ public IObservableCollection ExtensionPacks { get; } =
+ new ObservableCollectionExtended();
+
+ private SourceCache extensionPackExtensionsSource =
+ new(ext => ext.PackageExtension.Author + ext.PackageExtension.Title + ext.PackageExtension.Reference);
+
+ public IObservableCollection<
+ SelectableItem
+ > SelectedExtensionPackExtensions { get; } =
+ new ObservableCollectionExtended>();
+
+ public SearchCollection<
+ SelectableItem,
+ string,
+ string
+ > ExtensionPackExtensionsSearchCollection { get; }
+
+ [ObservableProperty]
+ private ExtensionPack? selectedExtensionPack;
[ObservableProperty]
private bool showNoExtensionsFoundMessage;
+ [ObservableProperty]
+ private bool areExtensionPacksLoading;
+
public PackageExtensionBrowserViewModel(
INotificationService notificationService,
ISettingsManager settingsManager,
- ServiceManager vmFactory
+ ServiceManager vmFactory,
+ IPrerequisiteHelper prerequisiteHelper
)
{
this.notificationService = notificationService;
this.settingsManager = settingsManager;
this.vmFactory = vmFactory;
+ this.prerequisiteHelper = prerequisiteHelper;
var availableItemsChangeSet = availableExtensionsSource
.Connect()
@@ -118,6 +151,25 @@ ServiceManager vmFactory
.ObserveOn(SynchronizationContext.Current)
.Subscribe();
+ extensionPackSource
+ .Connect()
+ .Bind(ExtensionPacks)
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe();
+
+ var extensionPackExtensionsChangeSet = extensionPackExtensionsSource
+ .Connect()
+ .Transform(ext => new SelectableItem(ext))
+ .ObserveOn(SynchronizationContext.Current!)
+ .Publish();
+
+ extensionPackExtensionsChangeSet
+ .AutoRefresh(item => item.IsSelected)
+ .Filter(item => item.IsSelected)
+ .Bind(SelectedExtensionPackExtensions)
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe();
+
cleanUp = new CompositeDisposable(
AvailableItemsSearchCollection = new SearchCollection<
SelectableItem,
@@ -142,10 +194,31 @@ ServiceManager vmFactory
? _ => true
: x => x.Item.Title.Contains(query, StringComparison.OrdinalIgnoreCase)
),
- installedItemsChangeSet.Connect()
+ installedItemsChangeSet.Connect(),
+ ExtensionPackExtensionsSearchCollection = new SearchCollection<
+ SelectableItem,
+ string,
+ string
+ >(
+ extensionPackExtensionsChangeSet,
+ query =>
+ string.IsNullOrWhiteSpace(query)
+ ? _ => true
+ : x =>
+ x.Item.PackageExtension.Title.Contains(query, StringComparison.OrdinalIgnoreCase)
+ ),
+ extensionPackExtensionsChangeSet.Connect()
);
}
+ ///
+ public override async Task OnLoadedAsync()
+ {
+ await base.OnLoadedAsync();
+ await LoadExtensionPacksAsync();
+ await Refresh();
+ }
+
public void AddExtensions(
IEnumerable packageExtensions,
IEnumerable installedExtensions
@@ -155,6 +228,16 @@ IEnumerable installedExtensions
installedExtensionsSource.AddOrUpdate(installedExtensions);
}
+ public void AddExtensionPacks(IEnumerable packs)
+ {
+ extensionPackSource.AddOrUpdate(packs);
+ SelectedExtensionPack = packs.FirstOrDefault();
+ if (SelectedExtensionPack == null)
+ return;
+
+ extensionPackExtensionsSource.AddOrUpdate(SelectedExtensionPack.Extensions);
+ }
+
[RelayCommand]
public async Task InstallSelectedExtensions()
{
@@ -299,14 +382,286 @@ public async Task OpenExtensionsSettingsDialog()
}
}
- ///
- public override async Task OnLoadedAsync()
+ [RelayCommand]
+ private async Task InstallExtensionPack()
{
- await base.OnLoadedAsync();
+ if (SelectedExtensionPack == null)
+ return;
+
+ var steps = new List();
+
+ foreach (var extension in SelectedExtensionPack.Extensions)
+ {
+ var installedExtension = installedExtensionsSource.Items.FirstOrDefault(
+ x =>
+ x.Definition?.Title == extension.PackageExtension.Title
+ && x.Definition.Reference == extension.PackageExtension.Reference
+ );
+
+ if (installedExtension != null)
+ {
+ steps.Add(
+ new UpdateExtensionStep(
+ PackagePair!.BasePackage.ExtensionManager!,
+ PackagePair.InstalledPackage,
+ installedExtension,
+ extension.Version
+ )
+ );
+ }
+ else
+ {
+ steps.Add(
+ new InstallExtensionStep(
+ PackagePair!.BasePackage.ExtensionManager!,
+ PackagePair!.InstalledPackage,
+ extension.PackageExtension,
+ extension.Version
+ )
+ );
+ }
+ }
+
+ var runner = new PackageModificationRunner
+ {
+ ShowDialogOnStart = true,
+ CloseWhenFinished = true,
+ ModificationCompleteMessage = $"Extension Pack {SelectedExtensionPack.Name} installed"
+ };
+ EventManager.Instance.OnPackageInstallProgressAdded(runner);
+ await runner.ExecuteSteps(steps);
await Refresh();
}
+ [RelayCommand]
+ public async Task CreateExtensionPackFromInstalled()
+ {
+ var extensions = SelectedInstalledItems.Select(x => x.Item).ToArray();
+ if (extensions.Length == 0)
+ return;
+
+ var (dialog, nameField) = GetNameEntryDialog();
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ {
+ var name = nameField.Text;
+ var newExtensionPack = new ExtensionPack
+ {
+ Name = name,
+ PackageType = PackagePair!.InstalledPackage.PackageName,
+ Extensions = SelectedInstalledItems
+ .Where(x => x.Item.Definition != null)
+ .Select(
+ x =>
+ new SavedPackageExtension
+ {
+ PackageExtension = x.Item.Definition,
+ Version = x.Item.Version
+ }
+ )
+ .ToList()
+ };
+
+ SaveExtensionPack(newExtensionPack, name);
+ await LoadExtensionPacksAsync();
+ notificationService.Show("Extension Pack Created", "The extension pack has been created");
+ }
+ }
+
+ [RelayCommand]
+ public async Task CreateExtensionPackFromAvailable()
+ {
+ var extensions = SelectedAvailableItems.Select(x => x.Item).ToArray();
+ if (extensions.Length == 0)
+ return;
+
+ var (dialog, nameField) = GetNameEntryDialog();
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ {
+ var name = nameField.Text;
+ var newExtensionPack = new ExtensionPack
+ {
+ Name = name,
+ PackageType = PackagePair!.InstalledPackage.PackageName,
+ Extensions = SelectedAvailableItems
+ .Select(x => new SavedPackageExtension { PackageExtension = x.Item, Version = null })
+ .ToList()
+ };
+
+ SaveExtensionPack(newExtensionPack, name);
+ await LoadExtensionPacksAsync();
+ notificationService.Show("Extension Pack Created", "The extension pack has been created");
+ }
+ }
+
+ [RelayCommand]
+ public async Task AddInstalledExtensionToPack(ExtensionPack pack)
+ {
+ foreach (var extension in SelectedInstalledItems)
+ {
+ if (
+ pack.Extensions.Any(
+ x =>
+ x.PackageExtension.Title == extension.Item.Definition?.Title
+ && x.PackageExtension.Author == extension.Item.Definition?.Author
+ && x.PackageExtension.Reference == extension.Item.Definition?.Reference
+ )
+ )
+ {
+ continue;
+ }
+
+ pack.Extensions.Add(
+ new SavedPackageExtension
+ {
+ PackageExtension = extension.Item.Definition!,
+ Version = extension.Item.Version
+ }
+ );
+ }
+
+ SaveExtensionPack(pack, pack.Name);
+ ClearSelection();
+ await LoadExtensionPacksAsync();
+ notificationService.Show(
+ "Extensions added to pack",
+ "The selected extensions have been added to the pack"
+ );
+ }
+
+ [RelayCommand]
+ public async Task AddExtensionToPack(ExtensionPack pack)
+ {
+ foreach (var extension in SelectedAvailableItems)
+ {
+ if (
+ pack.Extensions.Any(
+ x =>
+ x.PackageExtension.Title == extension.Item.Title
+ && x.PackageExtension.Author == extension.Item.Author
+ && x.PackageExtension.Reference == extension.Item.Reference
+ )
+ )
+ {
+ continue;
+ }
+
+ pack.Extensions.Add(
+ new SavedPackageExtension { PackageExtension = extension.Item, Version = null }
+ );
+ }
+
+ SaveExtensionPack(pack, pack.Name);
+ ClearSelection();
+ await LoadExtensionPacksAsync();
+ notificationService.Show(
+ "Extensions added to pack",
+ "The selected extensions have been added to the pack"
+ );
+ }
+
+ [RelayCommand]
+ public async Task RemoveExtensionFromPack()
+ {
+ if (SelectedExtensionPack is null)
+ return;
+
+ foreach (var extension in SelectedExtensionPackExtensions)
+ {
+ extensionPackExtensionsSource.Remove(extension.Item);
+ SelectedExtensionPack.Extensions.Remove(extension.Item);
+ }
+
+ SaveExtensionPack(SelectedExtensionPack, SelectedExtensionPack.Name);
+ ClearSelection();
+ await LoadExtensionPacksAsync();
+ }
+
+ [RelayCommand]
+ private async Task DeleteExtensionPackAsync(ExtensionPack pack)
+ {
+ var pathToDelete = settingsManager
+ .ExtensionPackDirectory.JoinDir(pack.PackageType)
+ .JoinFile($"{pack.Name}.json");
+
+ var confirmDeleteVm = vmFactory.Get();
+ confirmDeleteVm.PathsToDelete = [pathToDelete];
+
+ if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)
+ {
+ return;
+ }
+
+ try
+ {
+ await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);
+ }
+ catch (Exception e)
+ {
+ notificationService.ShowPersistent("Error deleting files", e.Message, NotificationType.Error);
+ return;
+ }
+
+ ClearSelection();
+ extensionPackSource.Remove(pack);
+ }
+
+ [RelayCommand]
+ private async Task OpenExtensionPackFolder()
+ {
+ var extensionPackDir = settingsManager.ExtensionPackDirectory.JoinDir(
+ PackagePair!.InstalledPackage.PackageName
+ );
+ if (!extensionPackDir.Exists)
+ {
+ extensionPackDir.Create();
+ }
+
+ if (SelectedExtensionPack is null || ExtensionPacks.Count <= 0)
+ {
+ await ProcessRunner.OpenFolderBrowser(extensionPackDir);
+ }
+ else
+ {
+ var extensionPackPath = extensionPackDir.JoinFile($"{SelectedExtensionPack.Name}.json");
+ await ProcessRunner.OpenFileBrowser(extensionPackPath);
+ }
+ }
+
+ [RelayCommand]
+ private async Task SetExtensionVersion(SavedPackageExtension selectedExtension)
+ {
+ if (SelectedExtensionPack is null)
+ return;
+
+ var vm = new GitVersionSelectorViewModel
+ {
+ GitVersionProvider = new CachedCommandGitVersionProvider(
+ selectedExtension.PackageExtension.Reference.ToString(),
+ prerequisiteHelper
+ )
+ };
+
+ var dialog = vm.GetDialog();
+
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ {
+ if (string.IsNullOrWhiteSpace(vm.SelectedGitVersion.ToString()))
+ return;
+
+ // update the version and save pack
+ selectedExtension.Version = new PackageExtensionVersion
+ {
+ Branch = vm.SelectedGitVersion.Branch,
+ CommitSha = vm.SelectedGitVersion.CommitSha,
+ Tag = vm.SelectedGitVersion.Tag
+ };
+ SaveExtensionPack(SelectedExtensionPack, SelectedExtensionPack.Name);
+
+ await LoadExtensionPacksAsync();
+ }
+ }
+
[RelayCommand]
public async Task Refresh()
{
@@ -355,11 +710,9 @@ private async Task RefreshCore()
using var _ = CodeTimer.StartDebug();
if (PackagePair?.BasePackage.ExtensionManager is not { } extensionManager)
- {
throw new NotSupportedException(
$"The package {PackagePair?.BasePackage} does not support extensions."
);
- }
var availableExtensions = (
await extensionManager.GetManifestExtensionsAsync(
@@ -391,14 +744,34 @@ await Task.Run(() =>
public void ClearSelection()
{
foreach (var item in SelectedAvailableItems.ToImmutableArray())
- {
item.IsSelected = false;
- }
foreach (var item in SelectedInstalledItems.ToImmutableArray())
- {
item.IsSelected = false;
- }
+
+ foreach (var item in SelectedExtensionPackExtensions.ToImmutableArray())
+ item.IsSelected = false;
+ }
+
+ private (BetterContentDialog dialog, TextBoxField nameField) GetNameEntryDialog()
+ {
+ var textFields = new TextBoxField[]
+ {
+ new()
+ {
+ Label = "Name",
+ Validator = text =>
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ throw new DataValidationException("Name is required");
+
+ if (ExtensionPacks.Any(pack => pack.Name == text))
+ throw new DataValidationException("Pack already exists");
+ }
+ }
+ };
+
+ return (DialogHelper.CreateTextEntryDialog("Pack Name", "", textFields), textFields[0]);
}
private async Task BeforeInstallCheck()
@@ -414,9 +787,9 @@ private async Task BeforeInstallCheck()
Title = "Installing Extensions",
Content = """
Extensions, the extension index, and their dependencies are community provided and not verified by the Stability Matrix team.
-
+
The install process may invoke external programs and scripts.
-
+
Please review the extension's source code and applicable licenses before installing.
""",
PrimaryButtonText = Resources.Action_Continue,
@@ -426,9 +799,7 @@ Please review the extension's source code and applicable licenses before install
};
if (await dialog.ShowAsync() != ContentDialogResult.Primary)
- {
return false;
- }
settingsManager.Transaction(
s => s.SeenTeachingTips.Add(Core.Models.Settings.TeachingTip.PackageExtensionsInstallNotice)
@@ -470,7 +841,6 @@ IList installedExtensions
var extensionsInstalled = new HashSet();
foreach (var (i, installedExt) in installedExtensions.Enumerate())
- {
if (
installedExt.GitRepositoryUrl is not null
&& repoToExtension.TryGetValue(
@@ -483,15 +853,78 @@ out var mappedExt
installedExtensions[i] = installedExt with { Definition = mappedExt };
}
- }
// For available extensions, add installed status if available
foreach (var (i, ext) in extensions.Enumerate())
- {
if (extensionsInstalled.Contains(ext))
- {
extensions[i] = ext with { IsInstalled = true };
+ }
+
+ private async Task LoadExtensionPacksAsync()
+ {
+ if (Design.IsDesignMode)
+ return;
+
+ try
+ {
+ AreExtensionPacksLoading = true;
+
+ var packDir = settingsManager.ExtensionPackDirectory;
+ if (!packDir.Exists)
+ packDir.Create();
+
+ var jsonFiles = packDir.EnumerateFiles("*.json", SearchOption.AllDirectories);
+ var packs = new List();
+
+ foreach (var jsonFile in jsonFiles)
+ {
+ var json = await jsonFile.ReadAllTextAsync();
+ try
+ {
+ var extensionPack = JsonSerializer.Deserialize(json);
+ if (
+ extensionPack != null
+ && extensionPack.PackageType == PackagePair!.InstalledPackage.PackageName
+ )
+ packs.Add(extensionPack);
+ }
+ catch (JsonException)
+ {
+ // ignored for now, need to log
+ }
}
+
+ extensionPackSource.AddOrUpdate(packs);
+ }
+ finally
+ {
+ AreExtensionPacksLoading = false;
+ }
+ }
+
+ private void SaveExtensionPack(ExtensionPack newExtensionPack, string name)
+ {
+ var extensionPackDir = settingsManager.ExtensionPackDirectory.JoinDir(newExtensionPack.PackageType);
+
+ if (!extensionPackDir.Exists)
+ {
+ extensionPackDir.Create();
+ }
+
+ var path = extensionPackDir.JoinFile($"{name}.json");
+ var json = JsonSerializer.Serialize(newExtensionPack);
+ path.WriteAllText(json);
+ }
+
+ partial void OnSelectedExtensionPackChanged(ExtensionPack? value)
+ {
+ if (value != null)
+ {
+ extensionPackExtensionsSource.Edit(updater => updater.Load(value.Extensions));
+ }
+ else
+ {
+ extensionPackExtensionsSource.Clear();
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallBrowserViewModel.cs
index e3bfeaa8a..08b5c42c6 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallBrowserViewModel.cs
@@ -7,6 +7,7 @@
using DynamicData.Alias;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Services;
@@ -24,18 +25,19 @@
namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
[View(typeof(PackageInstallBrowserView))]
-[Transient, ManagedService]
-public partial class PackageInstallBrowserViewModel : PageViewModelBase
+[ManagedService]
+[RegisterTransient]
+public partial class PackageInstallBrowserViewModel(
+ IPackageFactory packageFactory,
+ INavigationService packageNavigationService,
+ ISettingsManager settingsManager,
+ INotificationService notificationService,
+ ILogger logger,
+ IPyRunner pyRunner,
+ IPrerequisiteHelper prerequisiteHelper,
+ IAnalyticsHelper analyticsHelper
+) : PageViewModelBase
{
- private readonly IPackageFactory packageFactory;
- private readonly INavigationService packageNavigationService;
- private readonly ISettingsManager settingsManager;
- private readonly INotificationService notificationService;
- private readonly ILogger logger;
- private readonly IPyRunner pyRunner;
- private readonly IPrerequisiteHelper prerequisiteHelper;
- private readonly IAnalyticsHelper analyticsHelper;
-
[ObservableProperty]
private bool showIncompatiblePackages;
@@ -50,25 +52,12 @@ public partial class PackageInstallBrowserViewModel : PageViewModelBase
public IObservableCollection TrainingPackages { get; } =
new ObservableCollectionExtended();
- public PackageInstallBrowserViewModel(
- IPackageFactory packageFactory,
- INavigationService packageNavigationService,
- ISettingsManager settingsManager,
- INotificationService notificationService,
- ILogger logger,
- IPyRunner pyRunner,
- IPrerequisiteHelper prerequisiteHelper,
- IAnalyticsHelper analyticsHelper
- )
+ public override string Title => "Add Package";
+ public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Add };
+
+ protected override void OnInitialLoaded()
{
- this.packageFactory = packageFactory;
- this.packageNavigationService = packageNavigationService;
- this.settingsManager = settingsManager;
- this.notificationService = notificationService;
- this.logger = logger;
- this.pyRunner = pyRunner;
- this.prerequisiteHelper = prerequisiteHelper;
- this.analyticsHelper = analyticsHelper;
+ base.OnInitialLoaded();
var incompatiblePredicate = this.WhenPropertyChanged(vm => vm.ShowIncompatiblePackages)
.Select(_ => new Func(p => p.IsCompatible || ShowIncompatiblePackages))
@@ -121,9 +110,6 @@ IAnalyticsHelper analyticsHelper
);
}
- public override string Title => "Add Package";
- public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Add };
-
public void OnPackageSelected(BasePackage? package)
{
if (package is null)
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
index 224c56615..ea6c09f7e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
-using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -24,7 +22,6 @@
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Analytics;
-using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
@@ -58,7 +55,8 @@ IAnalyticsHelper analyticsHelper
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 string? ReleaseTooltipText =>
+ ShowReleaseMode ? null : Resources.Label_ReleasesUnavailableForThisPackage;
public bool ShowTorchIndexOptions => SelectedTorchIndex != TorchIndex.None;
@@ -70,7 +68,7 @@ IAnalyticsHelper analyticsHelper
private bool showDuplicateWarning;
[ObservableProperty]
- [NotifyPropertyChangedFor(nameof(ReleaseLabelText))]
+ [NotifyPropertyChangedFor(nameof(ReleaseTooltipText))]
private bool isReleaseMode;
[ObservableProperty]
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
index abb97742b..50dbf7990 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
@@ -4,6 +4,7 @@
using DynamicData;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -16,7 +17,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(PackageManagerPage))]
-[Singleton]
+[RegisterSingleton]
public partial class PackageManagerViewModel : PageViewModelBase
{
public override string Title => Resources.Label_Packages;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs
index 40f937d22..04809ec28 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs
@@ -3,15 +3,18 @@
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Progress;
public class DownloadProgressItemViewModel : PausableProgressItemViewModelBase
{
+ private readonly ITrackedDownloadService downloadService;
private readonly TrackedDownload download;
- public DownloadProgressItemViewModel(TrackedDownload download)
+ public DownloadProgressItemViewModel(ITrackedDownloadService downloadService, TrackedDownload download)
{
+ this.downloadService = downloadService;
this.download = download;
Id = download.Id;
@@ -42,7 +45,7 @@ public DownloadProgressItemViewModel(TrackedDownload download)
private void OnProgressStateChanged(ProgressState state)
{
- if (state == ProgressState.Inactive)
+ if (state is ProgressState.Inactive or ProgressState.Paused)
{
Progress.Text = "Paused";
}
@@ -62,6 +65,10 @@ private void OnProgressStateChanged(ProgressState state)
{
Progress.Text = "Failed";
}
+ else if (state == ProgressState.Pending)
+ {
+ Progress.Text = "Waiting for other downloads to finish";
+ }
}
///
@@ -75,13 +82,13 @@ public override Task Cancel()
public override Task Pause()
{
download.Pause();
+ State = ProgressState.Paused;
return Task.CompletedTask;
}
///
public override Task Resume()
{
- download.Resume();
- return Task.CompletedTask;
+ return downloadService.TryResumeDownload(download);
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs
index d3e9f1b6e..1be210a93 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs
@@ -12,6 +12,7 @@
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using FluentIcons.Common;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
@@ -34,9 +35,10 @@ namespace StabilityMatrix.Avalonia.ViewModels.Progress;
[View(typeof(ProgressManagerPage))]
[ManagedService]
-[Singleton]
+[RegisterSingleton]
public partial class ProgressManagerViewModel : PageViewModelBase
{
+ private readonly ITrackedDownloadService trackedDownloadService;
private readonly INotificationService notificationService;
private readonly INavigationService navigationService;
private readonly INavigationService settingsNavService;
@@ -58,6 +60,7 @@ public ProgressManagerViewModel(
INavigationService settingsNavService
)
{
+ this.trackedDownloadService = trackedDownloadService;
this.notificationService = notificationService;
this.navigationService = navigationService;
this.settingsNavService = settingsNavService;
@@ -186,7 +189,7 @@ await notificationService.ShowPersistentAsync(
}
};
- var vm = new DownloadProgressItemViewModel(e);
+ var vm = new DownloadProgressItemViewModel(trackedDownloadService, e);
ProgressItems.Add(vm);
}
@@ -197,7 +200,7 @@ public void AddDownloads(IEnumerable downloads)
{
if (ProgressItems.Any(vm => vm.Id == download.Id))
continue;
- var vm = new DownloadProgressItemViewModel(download);
+ var vm = new DownloadProgressItemViewModel(trackedDownloadService, download);
ProgressItems.Add(vm);
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/RefreshBadgeViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RefreshBadgeViewModel.cs
index b624f1e62..19eb9993e 100644
--- a/StabilityMatrix.Avalonia/ViewModels/RefreshBadgeViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/RefreshBadgeViewModel.cs
@@ -5,6 +5,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Styles;
@@ -17,7 +18,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(RefreshBadge))]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[ManagedService]
-[Transient]
+[RegisterTransient]
public partial class RefreshBadgeViewModel : ViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs
index 6e32f7359..635b7c67b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs
@@ -10,6 +10,7 @@
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
@@ -30,7 +31,8 @@
namespace StabilityMatrix.Avalonia.ViewModels.Settings;
[View(typeof(AccountSettingsPage))]
-[Singleton, ManagedService]
+[ManagedService]
+[RegisterSingleton]
public partial class AccountSettingsViewModel : PageViewModelBase
{
private readonly IAccountsService accountsService;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/AnalyticsSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/AnalyticsSettingsViewModel.cs
index 1240259b7..06bb419b4 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/AnalyticsSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/AnalyticsSettingsViewModel.cs
@@ -1,5 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -10,7 +11,8 @@
namespace StabilityMatrix.Avalonia.ViewModels.Settings;
[View(typeof(AnalyticsSettingsPage))]
-[Singleton, ManagedService]
+[ManagedService]
+[RegisterSingleton]
public partial class AnalyticsSettingsViewModel : PageViewModelBase
{
public override string Title => Resources.Label_Analytics;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
index dc5cc078b..bf54fbdcd 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
@@ -13,6 +13,7 @@
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using NLog;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models.Inference;
@@ -31,7 +32,8 @@
namespace StabilityMatrix.Avalonia.ViewModels.Settings;
[View(typeof(InferenceSettingsPage))]
-[Singleton, ManagedService]
+[ManagedService]
+[RegisterSingleton]
public partial class InferenceSettingsViewModel : PageViewModelBase
{
private readonly INotificationService notificationService;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index 816d59331..06aa20b2d 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -25,11 +25,13 @@
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using Microsoft.Win32;
using NLog;
using SkiaSharp;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Controls;
+using StabilityMatrix.Avalonia.DesignData;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Helpers;
using StabilityMatrix.Avalonia.Languages;
@@ -37,12 +39,14 @@
using StabilityMatrix.Avalonia.Models.TagCompletion;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
+using StabilityMatrix.Avalonia.ViewModels.Controls;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Avalonia.Views.Settings;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Git;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo;
using StabilityMatrix.Core.Models;
@@ -60,7 +64,8 @@
namespace StabilityMatrix.Avalonia.ViewModels.Settings;
[View(typeof(MainSettingsPage))]
-[Singleton, ManagedService]
+[ManagedService]
+[RegisterSingleton]
public partial class MainSettingsViewModel : PageViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -148,11 +153,20 @@ public partial class MainSettingsViewModel : PageViewModelBase
[ObservableProperty]
private bool moveFilesOnImport;
+ [ObservableProperty]
+ private int maxConcurrentDownloads;
+
#region System Settings
[ObservableProperty]
private bool isWindowsLongPathsEnabled;
+ [ObservableProperty]
+ private ObservableCollection gpuInfoCollection = [];
+
+ [ObservableProperty]
+ private GpuInfo? preferredGpu;
+
#endregion
#region System Info
@@ -184,6 +198,7 @@ public partial class MainSettingsViewModel : PageViewModelBase
$"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options.";
public string DataDirectory => settingsManager.IsLibraryDirSet ? settingsManager.LibraryDir : "Not set";
+ public string ModelsDirectory => settingsManager.ModelsDirectory;
public MainSettingsViewModel(
INotificationService notificationService,
@@ -278,6 +293,20 @@ IAccountsService accountsService
true
);
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.PreferredGpu,
+ settings => settings.PreferredGpu,
+ true
+ );
+
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.MaxConcurrentDownloads,
+ settings => settings.MaxConcurrentDownloads,
+ true
+ );
+
DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);
hardwareInfoUpdateTimer.Tick += OnHardwareInfoUpdateTimerTick;
@@ -311,6 +340,13 @@ public override async Task OnLoadedAsync()
await notificationService.TryAsync(completionProvider.Setup());
+ var gpuInfos = HardwareHelper.IterGpuInfo();
+ GpuInfoCollection = new ObservableCollection(gpuInfos);
+ PreferredGpu ??=
+ GpuInfos.FirstOrDefault(
+ gpu => gpu.Name?.Contains("nvidia", StringComparison.InvariantCultureIgnoreCase) ?? false
+ ) ?? GpuInfos.FirstOrDefault();
+
// Start accounts update
accountsService
.RefreshAsync()
@@ -418,6 +454,11 @@ partial void OnRemoveSymlinksOnShutdownChanged(bool value)
settingsManager.Transaction(s => s.RemoveFolderLinksOnShutdown = value);
}
+ partial void OnMaxConcurrentDownloadsChanged(int value)
+ {
+ trackedDownloadService.UpdateMaxConcurrentDownloads(value);
+ }
+
public async Task ResetCheckpointCache()
{
await notificationService.TryAsync(modelIndexService.RefreshIndex());
@@ -785,6 +826,33 @@ public async Task PickNewDataDirectory()
}
}
+ public async Task PickNewModelsFolder()
+ {
+ var provider = App.StorageProvider;
+ var result = await provider.OpenFolderPickerAsync(new FolderPickerOpenOptions());
+
+ if (result.Count == 0)
+ return;
+
+ var newPath = (result[0].Path.LocalPath);
+ settingsManager.Transaction(s => s.ModelDirectoryOverride = newPath);
+ SharedFolders.SetupSharedModelFolders(newPath);
+
+ // Restart
+ var restartDialog = new BetterContentDialog
+ {
+ Title = "Restart required",
+ Content = "Stability Matrix must be restarted for the changes to take effect.",
+ PrimaryButtonText = Resources.Action_Restart,
+ DefaultButton = ContentDialogButton.Primary,
+ IsSecondaryButtonEnabled = false,
+ };
+ await restartDialog.ShowAsync();
+
+ Process.Start(Compat.AppCurrentPath);
+ App.Shutdown();
+ }
+
#endregion
#region Debug Section
@@ -873,6 +941,12 @@ private async Task DebugThrowAsyncException()
throw new ApplicationException("Example Message");
}
+ [RelayCommand]
+ private void DebugThrowDispatcherException()
+ {
+ Dispatcher.UIThread.Post(() => throw new OperationCanceledException("Example Message"));
+ }
+
[RelayCommand]
private async Task DebugMakeImageGrid()
{
@@ -982,7 +1056,7 @@ private async Task DebugTrackedDownload()
var url = textFields[0].Text;
var filePath = textFields[1].Text;
var download = trackedDownloadService.NewDownload(new Uri(url), new FilePath(filePath));
- download.Start();
+ await trackedDownloadService.TryStartDownload(download);
}
}
#endregion
@@ -1002,8 +1076,37 @@ private async Task DebugTrackedDownload()
new CommandItem(DebugExtractImagePromptsToTxtCommand),
new CommandItem(DebugShowConfirmDeleteDialogCommand),
new CommandItem(DebugShowModelMetadataEditorDialogCommand),
+ new CommandItem(DebugNvidiaSmiCommand),
+ new CommandItem(DebugShowGitVersionSelectorDialogCommand),
+ new CommandItem(DebugShowMockGitVersionSelectorDialogCommand),
];
+ [RelayCommand]
+ private async Task DebugShowGitVersionSelectorDialog()
+ {
+ var vm = new GitVersionSelectorViewModel
+ {
+ GitVersionProvider = new CachedCommandGitVersionProvider(
+ "https://github.com/ltdrdata/ComfyUI-Manager",
+ prerequisiteHelper
+ )
+ };
+ var dialog = vm.GetDialog();
+
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ {
+ notificationService.ShowPersistent("Selected version", $"{vm.SelectedGitVersion}");
+ }
+ }
+
+ [RelayCommand]
+ private async Task DebugShowMockGitVersionSelectorDialog()
+ {
+ var vm = new GitVersionSelectorViewModel { GitVersionProvider = new MockGitVersionProvider() };
+ var dialog = vm.GetDialog();
+ await dialog.ShowAsync();
+ }
+
[RelayCommand]
private async Task DebugShowModelMetadataEditorDialog()
{
@@ -1233,6 +1336,12 @@ private async Task DebugShowImageMaskEditor()
await vm.GetDialog().ShowAsync();
}
+ [RelayCommand]
+ private void DebugNvidiaSmi()
+ {
+ HardwareHelper.IterGpuInfoNvidiaSmi();
+ }
+
#endregion
#region Systems Setting Section
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/NotificationSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/NotificationSettingsViewModel.cs
index fc0c21a9a..708b3b89c 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/NotificationSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/NotificationSettingsViewModel.cs
@@ -3,6 +3,7 @@
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Settings;
using StabilityMatrix.Core.Attributes;
@@ -12,7 +13,7 @@
namespace StabilityMatrix.Avalonia.ViewModels.Settings;
[View(typeof(NotificationSettingsPage))]
-[Singleton]
+[RegisterSingleton]
[ManagedService]
public partial class NotificationSettingsViewModel(ISettingsManager settingsManager) : PageViewModelBase
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/UpdateSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/UpdateSettingsViewModel.cs
index 0b358985b..50002c8ed 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/UpdateSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/UpdateSettingsViewModel.cs
@@ -10,6 +10,7 @@
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using FluentIcons.Common;
+using Injectio.Attributes;
using Semver;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
@@ -27,7 +28,8 @@
namespace StabilityMatrix.Avalonia.ViewModels.Settings;
[View(typeof(UpdateSettingsPage))]
-[Singleton, ManagedService]
+[ManagedService]
+[RegisterSingleton]
public partial class UpdateSettingsViewModel : PageViewModelBase
{
private readonly IUpdateHelper updateHelper;
diff --git a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
index 665b148e0..4b97904b8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
@@ -4,6 +4,7 @@
using DynamicData;
using FluentAvalonia.UI.Controls;
using FluentIcons.Common;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Settings;
@@ -15,7 +16,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(SettingsPage))]
-[Singleton]
+[RegisterSingleton]
public partial class SettingsViewModel : PageViewModelBase
{
public override string Title => "Settings";
diff --git a/StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs
index 3db3dabb2..fe6c8b642 100644
--- a/StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs
@@ -3,6 +3,7 @@
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
+using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
@@ -12,7 +13,7 @@
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(WorkflowsPage))]
-[Singleton]
+[RegisterSingleton]
public partial class WorkflowsPageViewModel : PageViewModelBase
{
public override string Title => Resources.Label_Workflows;
diff --git a/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs b/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs
index 9b2f245be..68b984b1e 100644
--- a/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs
+++ b/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs
@@ -1,9 +1,9 @@
-using StabilityMatrix.Avalonia.Controls;
-using StabilityMatrix.Core.Attributes;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Controls;
namespace StabilityMatrix.Avalonia.Views;
-[Singleton]
+[RegisterSingleton]
public partial class CheckpointBrowserPage : UserControlBase
{
public CheckpointBrowserPage()
diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
index 079745ea7..709ffaf36 100644
--- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
@@ -624,12 +624,24 @@
IsHitTestVisible="False">
-
+
+
+
+
]
public partial class CheckpointsPage : UserControlBase
{
private Dictionary dragTimers = new();
diff --git a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml
index 7d7cda10d..b10d36526 100644
--- a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml
@@ -94,6 +94,11 @@
+