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 HumanizeFileSizeConverter { get; } = + new(value => + { + var size = Convert.ToDouble(value); + string[] sizeUnits = ["B", "KB", "MB", "GB", "TB"]; + var unitIndex = 0; + while (size >= 1000 && unitIndex < sizeUnits.Length - 1) + { + size /= 1000; + unitIndex++; + } + return $"{size:0.##} {sizeUnits[unitIndex]}"; + }); + + public static FuncValueConverter HumanizeBinaryFileSizeConverter { get; } = + new(value => + { + var size = Convert.ToDouble(value); + string[] sizeUnits = ["B", "KiB", "MiB", "GiB", "TiB"]; + var unitIndex = 0; + while (size >= 1024 && unitIndex < sizeUnits.Length - 1) + { + size /= 1024; + unitIndex++; + } + return $"{size:0.##} {sizeUnits[unitIndex]}"; + }); +} diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 6b1de7c24..7874e75a2 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -371,9 +371,25 @@ public static void Initialize() ), }; - ProgressManagerViewModel.ProgressItems.AddRange( - new ProgressItemViewModelBase[] + var packageInstall = new PackageInstallProgressItemViewModel( + new PackageModificationRunner + { + CurrentProgress = new ProgressReport(0.5f, "Installing package...", "Installing... 50%"), + ModificationCompleteMessage = "Package installed successfully" + } + ) + { + Progress = new ContentDialogProgressViewModelBase { + Value = 50, + IsIndeterminate = false, + Text = "UwU Install", + Description = "Installing...", + } + }; + + ProgressManagerViewModel.ProgressItems.AddRange( + [ new ProgressItemViewModel( new ProgressItem( Guid.NewGuid(), @@ -384,20 +400,27 @@ public static void Initialize() new MockDownloadProgressItemViewModel( "Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe" ), - new PackageInstallProgressItemViewModel( - new PackageModificationRunner + new MockDownloadProgressItemViewModel( + "Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe" + ) + { + Progress = new ContentDialogProgressViewModelBase { - CurrentProgress = new ProgressReport(0.5f, "Installing package..."), - ModificationCompleteMessage = "Package installed successfully" + Value = 50, + IsIndeterminate = false, + Text = "Waiting on other downloads to finish", + Description = "Waiting on other downloads to finish", } - ) - } + }, + packageInstall + ] ); UpdateViewModel = Services.GetRequiredService(); UpdateViewModel.CurrentVersionText = "v2.0.0"; UpdateViewModel.NewVersionText = "v2.0.1"; - UpdateViewModel.ReleaseNotes = "## v2.0.1\n- Fixed a bug\n- Added a feature\n- Removed a feature"; + UpdateViewModel.ReleaseNotes = + "## v2.0.1\n- Fixed a bug\n- Added a feature\n- Removed a feature\n - Did some `--code` stuff"; isInitialized = true; } @@ -557,6 +580,33 @@ public static MainPackageManagerViewModel MainPackageManagerViewModel new InstalledPackageExtension { Paths = [new DirectoryPath("example-dir-2")] } ] ); + vm.AddExtensionPacks( + [ + new ExtensionPack + { + Name = "Test Pack", + PackageType = "ComfyUI", + Extensions = + [ + new SavedPackageExtension + { + PackageExtension = new PackageExtension + { + Author = "TestAuthor", + Title = "Test", + Reference = new Uri("https://github.com/LykosAI/StabilityMatrix"), + Files = [new Uri("https://github.com/LykosAI/StabilityMatrix")] + }, + Version = new PackageExtensionVersion + { + Branch = "main", + CommitSha = "abcd123" + } + } + ] + } + ] + ); }); public static CheckpointsPageViewModel CheckpointsPageViewModel => @@ -1130,6 +1180,8 @@ public static CompletionList SampleCompletionList vm.BaseModelType = CivitBaseModelType.Pony; }); + public static MockGitVersionProvider MockGitVersionProvider => new(); + public static string CurrentDirectory => Directory.GetCurrentDirectory(); public static Indexer Types { get; } = new(); diff --git a/StabilityMatrix.Avalonia/DesignData/MockGitVersionProvider.cs b/StabilityMatrix.Avalonia/DesignData/MockGitVersionProvider.cs new file mode 100644 index 000000000..d6956dc3d --- /dev/null +++ b/StabilityMatrix.Avalonia/DesignData/MockGitVersionProvider.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using StabilityMatrix.Core.Git; +using StabilityMatrix.Core.Models; + +namespace StabilityMatrix.Avalonia.DesignData; + +public class MockGitVersionProvider : IGitVersionProvider +{ + public Task> FetchTagsAsync( + int limit = 0, + CancellationToken cancellationToken = default + ) + { + return Task.FromResult>( + [ + new GitVersion { Tag = "v1.0.0" }, + new GitVersion { Tag = "v1.0.1" }, + new GitVersion { Tag = "v1.0.2" }, + new GitVersion { Tag = "v1.0.3" } + ] + ); + } + + public Task> FetchBranchesAsync( + int limit = 0, + CancellationToken cancellationToken = default + ) + { + return Task.FromResult>( + [ + new GitVersion { Branch = "main" }, + new GitVersion { Branch = "develop" }, + new GitVersion { Branch = "feature/1" }, + new GitVersion { Branch = "feature/2" } + ] + ); + } + + public Task> FetchCommitsAsync( + string? branch = null, + int limit = 0, + CancellationToken cancellationToken = default + ) + { + branch ??= "main"; + + if (limit <= 0) + { + limit = 100; + } + + // Generate sha1 hashes using branch as rng seed + var rng = new Random(branch.GetHashCode()); + var hashes = Enumerable + .Range(0, limit) + .Select(_ => + { + var data = new byte[32]; + rng.NextBytes(data); + var hash = SHA1.HashData(data); + return Convert.ToHexString(hash).ToLowerInvariant(); + }) + .ToArray(); + + var results = hashes.Select(hash => new GitVersion { Branch = branch, CommitSha = hash }).ToArray(); + + return Task.FromResult>(results); + } +} diff --git a/StabilityMatrix.Avalonia/DialogHelper.cs b/StabilityMatrix.Avalonia/DialogHelper.cs index f4c79278c..345a7cf54 100644 --- a/StabilityMatrix.Avalonia/DialogHelper.cs +++ b/StabilityMatrix.Avalonia/DialogHelper.cs @@ -19,7 +19,6 @@ using AvaloniaEdit.TextMate; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Controls; -using Markdown.Avalonia; using NLog; using Refit; using StabilityMatrix.Avalonia.Controls; diff --git a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.Reflection.cs b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.Reflection.cs new file mode 100644 index 000000000..364b0fd41 --- /dev/null +++ b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.Reflection.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using Injectio.Attributes; +using Microsoft.Extensions.DependencyInjection; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Helpers; + +internal static partial class AttributeServiceInjector +{ + /// + /// Registers services from the assemblies starting with "StabilityMatrix" using + /// and . + /// + /// + /// The to which the services will be registered. + /// + /// + /// The assemblies to scan for services. + /// + // Injectio.Attributes are conditional with the required `REGISTER_SERVICE_USAGES` compilation symbol + [Conditional("REGISTER_SERVICE_USAGES")] + public static void AddServicesByAttributesReflection( + IServiceCollection services, + IEnumerable assemblies + ) + { + var exportedTypes = assemblies.SelectMany(a => a.GetExportedTypes()).ToArray(); + + var transientTypes = exportedTypes + .Select( + t => new { t, attributes = t.GetCustomAttributes(typeof(RegisterTransientAttribute), false) } + ) + .Where(t1 => t1.attributes is { Length: > 0 }) + .Select(t1 => new { Type = t1.t, Attribute = (RegisterTransientAttribute)t1.attributes[0] }) + .ToArray(); + + foreach (var typePair in transientTypes) + { + if ( + typePair.Attribute.ServiceType is not null + && typePair.Attribute.ImplementationType is not null + ) + { + services.AddTransient(typePair.Attribute.ServiceType, typePair.Attribute.ImplementationType); + } + else if (typePair.Attribute.ServiceType is not null) + { + services.AddTransient(typePair.Attribute.ServiceType, typePair.Type); + } + else + { + services.AddTransient(typePair.Type); + } + } + + var singletonTypes = exportedTypes + .Select( + t => new { t, attributes = t.GetCustomAttributes(typeof(RegisterSingletonAttribute), 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() + } + ) + .ToArray(); + + foreach (var typePair in singletonTypes) + { + foreach (var attribute in typePair.Attributes) + { + if (attribute.ServiceType is not null && attribute.ImplementationType is not null) + { + services.AddSingleton(attribute.ServiceType, attribute.ImplementationType); + } + else if (attribute.ServiceType is not null) + { + services.AddSingleton(attribute.ServiceType, typePair.Type); + } + else + { + services.AddSingleton(typePair.Type); + } + } + } + } + + /// + /// Registers services from the assemblies starting with "StabilityMatrix" using + /// and . + /// + /// + /// The to which the services will be registered. + /// + /// + /// The assemblies to scan for services. + /// + public static void AddServicesByAttributesReflectionOld( + this IServiceCollection services, + IEnumerable assemblies + ) + { + var exportedTypes = assemblies.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) + ); + } + } + } + } +} diff --git a/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs new file mode 100644 index 000000000..311c19503 --- /dev/null +++ b/StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs @@ -0,0 +1,93 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using StabilityMatrix.Avalonia.Services; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Helper; + +namespace StabilityMatrix.Avalonia.Helpers; + +/// +/// Registers services using and attributes. +/// +[Localizable(false)] +internal static partial class AttributeServiceInjector +{ + /// + /// Registers services from the assemblies using Attributes via source generation. + /// - Uses Source Generation by default (). + /// If `REGISTER_SERVICE_REFLECTION` symbol is defined, (also requires `REGISTER_SERVICE_USAGES` symbol) then: + /// - Uses Reflection (). + /// + /// + /// The to which the services will be registered. + /// + public static IServiceCollection AddServicesByAttributes(this IServiceCollection services) + { +#if REGISTER_SERVICE_REFLECTION + var assemblies = AppDomain + .CurrentDomain.GetAssemblies() + .Where(a => a.FullName?.StartsWith("StabilityMatrix") == true); + AddServicesByAttributesReflection(services, assemblies); +#else + AddServicesByAttributesSourceGen(services); +#endif + return services; + } + + /// + /// Registers services from the assemblies using Attributes via source generation. + /// + /// + /// The to which the services will be registered. + /// + public static void AddServicesByAttributesSourceGen(IServiceCollection services) + { + services.AddStabilityMatrixCore(); + services.AddStabilityMatrixAvalonia(); + } + + /// + /// Adds a to the . + /// + /// The to which the will be added. + /// An optional filter for the services. + /// The base type of the services. + /// + public static IServiceCollection AddServiceManagerWithCurrentCollectionServices( + this IServiceCollection services, + Func? serviceFilter = null + ) + { + return services.AddSingleton>(provider => + { + using var _ = CodeTimer.StartDebug( + callerName: $"{nameof(AddServiceManagerWithCurrentCollectionServices)}<{typeof(TService)}>" + ); + + var serviceManager = new ServiceManager(); + + // Get registered services that are assignable to TService + var serviceDescriptors = services.Where(s => s.ServiceType.IsAssignableTo(typeof(TService))); + + // Optional filter + if (serviceFilter is not null) + { + serviceDescriptors = serviceDescriptors.Where(serviceFilter); + } + + foreach (var service in serviceDescriptors) + { + var type = service.ServiceType; + Debug.Assert(type is not null, "type is not null"); + Debug.Assert(type.IsAssignableTo(typeof(TService)), "type is assignable to TService"); + + serviceManager.Register(type, () => (TService)provider.GetRequiredService(type)); + } + + return serviceManager; + }); + } +} diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index c3c9dd725..f3d049005 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -45,6 +45,8 @@ IPyRunner pyRunner private const string CppBuildToolsUrl = "https://aka.ms/vs/17/release/vs_BuildTools.exe"; + private const string HipSdkDownloadUrl = "https://cdn.lykos.ai/AMD-HIP-SDK.exe"; + private string HomeDir => settingsManager.LibraryDir; private string VcRedistDownloadPath => Path.Combine(HomeDir, "vcredist.x64.exe"); @@ -83,6 +85,11 @@ IPyRunner pyRunner "BuildTools" ); + private string HipSdkDownloadPath => Path.Combine(AssetsDir, "AMD-HIP-SDK.exe"); + + private string HipInstalledPath => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "AMD", "ROCm", "5.7"); + public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin"); public bool IsPythonInstalled => File.Exists(PythonDllPath); @@ -192,6 +199,11 @@ public async Task InstallPackageRequirements( { await InstallVcBuildToolsIfNecessary(progress); } + + if (prerequisites.Contains(PackagePrerequisite.HipSdk)) + { + await InstallHipSdkIfNecessary(progress); + } } public async Task InstallAllIfNecessary(IProgress? progress = null) @@ -202,6 +214,7 @@ public async Task InstallAllIfNecessary(IProgress? progress = nu await InstallGitIfNecessary(progress); await InstallNodeIfNecessary(progress); await InstallVcBuildToolsIfNecessary(progress); + await InstallHipSdkIfNecessary(progress); } public async Task UnpackResourcesIfNecessary(IProgress? progress = null) @@ -553,6 +566,39 @@ await downloadService.DownloadToFileAsync( await process.WaitForExitAsync(); } + [SupportedOSPlatform("windows")] + public async Task InstallHipSdkIfNecessary(IProgress? progress = null) + { + if (Directory.Exists(HipInstalledPath)) + return; + + await downloadService.DownloadToFileAsync(HipSdkDownloadUrl, HipSdkDownloadPath, progress: progress); + Logger.Info("Downloaded & installing HIP SDK"); + + progress?.Report( + new ProgressReport( + progress: 0.5f, + isIndeterminate: true, + type: ProgressType.Generic, + message: "Installing HIP SDK, this may take a few minutes..." + ) + ); + + var info = new ProcessStartInfo + { + FileName = HipSdkDownloadPath, + Arguments = "-install -log hip_install.log", + UseShellExecute = true, + CreateNoWindow = true, + Verb = "runas" + }; + + if (Process.Start(info) is { } process) + { + await process.WaitForExitAsync(); + } + } + public async Task RunDotnet( ProcessArgs args, string? workingDirectory = null, diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs index 1b7db9e3b..4b1fcef91 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs +++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs @@ -617,6 +617,24 @@ public static string Action_ShowInExplorer { } } + /// + /// Looks up a localized string similar to Show Log in Explorer. + /// + public static string Action_ShowLogInExplorer { + get { + return ResourceManager.GetString("Action_ShowLogInExplorer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show Log in Finder. + /// + public static string Action_ShowLogInFinder { + get { + return ResourceManager.GetString("Action_ShowLogInFinder", resourceCulture); + } + } + /// /// Looks up a localized string similar to Signup. /// @@ -749,6 +767,15 @@ public static string Label_Addons { } } + /// + /// Looks up a localized string similar to Add to Existing Pack. + /// + public static string Label_AddToExistingPack { + get { + return ResourceManager.GetString("Label_AddToExistingPack", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add Stability Matrix to the Start Menu. /// @@ -1280,6 +1307,15 @@ public static string Label_CopyLinkToClipboard { } } + /// + /// Looks up a localized string similar to To create one, simply select the extensions you want from the 'Available Extensions' or 'Installed Extensions' tab and click 'Save'. + /// + public static string Label_CreateExtensionPackExplanation { + get { + return ResourceManager.GetString("Label_CreateExtensionPackExplanation", resourceCulture); + } + } + /// /// Looks up a localized string similar to Current directory:. /// @@ -1550,6 +1586,15 @@ public static string Label_EverythingLooksGood { } } + /// + /// Looks up a localized string similar to Extension Packs. + /// + public static string Label_ExtensionPacks { + get { + return ResourceManager.GetString("Label_ExtensionPacks", resourceCulture); + } + } + /// /// Looks up a localized string similar to Extra Networks (Lora / LyCORIS). /// @@ -1748,6 +1793,15 @@ public static string Label_ImportAsConnectedExplanation { } } + /// + /// Looks up a localized string similar to Add a .json extension pack file to the ExtensionPacks folder within your Data directory.. + /// + public static string Label_ImportExtensionPacksExplanation { + get { + return ResourceManager.GetString("Label_ImportExtensionPacksExplanation", resourceCulture); + } + } + /// /// Looks up a localized string similar to Import Latest -. /// @@ -1838,6 +1892,15 @@ public static string Label_Installed { } } + /// + /// Looks up a localized string similar to Install Extension Pack. + /// + public static string Label_InstallExtensionPack { + get { + return ResourceManager.GetString("Label_InstallExtensionPack", resourceCulture); + } + } + /// /// Looks up a localized string similar to Installing. /// @@ -2063,6 +2126,15 @@ public static string Label_NetworksLoraOrLycoris { } } + /// + /// Looks up a localized string similar to New Extension Pack. + /// + public static string Label_NewExtensionPack { + get { + return ResourceManager.GetString("Label_NewExtensionPack", resourceCulture); + } + } + /// /// Looks up a localized string similar to New Folder. /// @@ -2117,6 +2189,15 @@ public static string Label_NodeDetails { } } + /// + /// Looks up a localized string similar to No Extension Packs Found. + /// + public static string Label_NoExtensionPacksFound { + get { + return ResourceManager.GetString("Label_NoExtensionPacksFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to No extensions found.. /// @@ -2216,6 +2297,24 @@ public static string Label_OpenArtBrowser { } } + /// + /// Looks up a localized string similar to Open Extension Packs Folder. + /// + public static string Label_OpenExtensionPacksFolder { + get { + return ResourceManager.GetString("Label_OpenExtensionPacksFolder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to - or -. + /// + public static string Label_OrWithHyphensAround { + get { + return ResourceManager.GetString("Label_OrWithHyphensAround", resourceCulture); + } + } + /// /// Looks up a localized string similar to Output Folder. /// @@ -2531,6 +2630,15 @@ public static string Label_Releases { } } + /// + /// Looks up a localized string similar to Releases are unavailable for this package.. + /// + public static string Label_ReleasesUnavailableForThisPackage { + get { + return ResourceManager.GetString("Label_ReleasesUnavailableForThisPackage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Remove shared checkpoints directory symbolic links on shutdown. /// @@ -2631,7 +2739,7 @@ public static string Label_SelectNewDataDirectory { } /// - /// Looks up a localized string similar to Does not move existing data. + /// Looks up a localized string similar to Does not move existing data. Requires application restart.. /// public static string Label_SelectNewDataDirectory_Details { get { @@ -3386,6 +3494,24 @@ public static string Text_SomeFilesCouldNotBeDeleted { } } + /// + /// Looks up a localized string similar to Please let us know about this issue with the details below and attach the zipped log files.. + /// + public static string Text_UnexpectedError_Description { + get { + return ResourceManager.GetString("Text_UnexpectedError_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You can continue, but full functionality will be available after restart. Please let us know about this issue with the details below and attach the zipped log files.. + /// + public static string Text_UnexpectedErrorRecoverable_Description { + get { + return ResourceManager.GetString("Text_UnexpectedErrorRecoverable_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Welcome to Stability Matrix!. /// diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx index 2568c6da8..12ccc82fc 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.resx @@ -472,7 +472,7 @@ Select new Data Directory - Does not move existing data + Does not move existing data. Requires application restart. Select Directory @@ -1260,4 +1260,46 @@ A restart may be required for system changes to take effect. + + Releases are unavailable for this package. + + + Please let us know about this issue with the details below and attach the zipped log files. + + + You can continue, but full functionality will be available after restart. Please let us know about this issue with the details below and attach the zipped log files. + + + Show Log in Explorer + + + Show Log in Finder + + + Extension Packs + + + No Extension Packs Found + + + Open Extension Packs Folder + + + Install Extension Pack + + + Add to Existing Pack + + + New Extension Pack + + + To create one, simply select the extensions you want from the 'Available Extensions' or 'Installed Extensions' tab and click 'Save' + + + Add a .json extension pack file to the ExtensionPacks folder within your Data directory. + + + - or - + diff --git a/StabilityMatrix.Avalonia/Models/AppArgs.cs b/StabilityMatrix.Avalonia/Models/AppArgs.cs index a47d17cb2..66441ad34 100644 --- a/StabilityMatrix.Avalonia/Models/AppArgs.cs +++ b/StabilityMatrix.Avalonia/Models/AppArgs.cs @@ -50,6 +50,12 @@ public class AppArgs [Option("reset-window-position", HelpText = "Reset the saved window position back to (0,0)")] public bool ResetWindowPosition { get; set; } + /// + /// Flag to enable the splash screen on startup + /// + [Option("splash-screen", HelpText = "Enable the startup splash screen")] + public bool IsSplashScreenEnabled { get; set; } + /// /// Flag for disabling hardware acceleration / GPU rendering /// diff --git a/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs b/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs index 134a3c0b0..f678ffc6b 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs @@ -2,7 +2,7 @@ namespace StabilityMatrix.Avalonia.Models.Inference; -[JsonConverter(typeof(JsonStringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum VideoOutputMethod { Fastest, diff --git a/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs b/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs index dc929f3c0..994efe3f7 100644 --- a/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs +++ b/StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs @@ -19,7 +19,7 @@ public class InferenceProjectDocument : ICloneable public int Version { get; set; } = 2; - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public InferenceProjectType ProjectType { get; set; } public JsonObject? State { get; set; } diff --git a/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs b/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs index 7620816cb..ef7506a18 100644 --- a/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs +++ b/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json.Serialization; using Avalonia.Platform.Storage; @@ -18,5 +19,9 @@ public class OpenArtMetadata public List? FilePath { get; set; } [JsonIgnore] + [MemberNotNullWhen(true, nameof(Workflow))] public bool HasMetadata => Workflow?.Creator != null; + + [JsonIgnore] + internal int Index { get; set; } } diff --git a/StabilityMatrix.Avalonia/Models/SharedState.cs b/StabilityMatrix.Avalonia/Models/SharedState.cs index 17fd32887..b979a4a99 100644 --- a/StabilityMatrix.Avalonia/Models/SharedState.cs +++ b/StabilityMatrix.Avalonia/Models/SharedState.cs @@ -1,12 +1,12 @@ using CommunityToolkit.Mvvm.ComponentModel; -using StabilityMatrix.Core.Attributes; +using Injectio.Attributes; namespace StabilityMatrix.Avalonia.Models; /// /// Singleton DI service for observable shared UI state. /// -[Singleton] +[RegisterSingleton] public partial class SharedState : ObservableObject { /// diff --git a/StabilityMatrix.Avalonia/Models/TagCompletion/CompletionProvider.cs b/StabilityMatrix.Avalonia/Models/TagCompletion/CompletionProvider.cs index eb2acf072..c1c46fb2b 100644 --- a/StabilityMatrix.Avalonia/Models/TagCompletion/CompletionProvider.cs +++ b/StabilityMatrix.Avalonia/Models/TagCompletion/CompletionProvider.cs @@ -10,12 +10,12 @@ using AutoComplete.DataStructure; using AutoComplete.Domain; using Avalonia.Controls.Notifications; +using Injectio.Attributes; using Nito.AsyncEx; using NLog; using StabilityMatrix.Avalonia.Controls.CodeCompletion; using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Avalonia.Services; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models; @@ -26,7 +26,7 @@ namespace StabilityMatrix.Avalonia.Models.TagCompletion; -[Singleton(typeof(ICompletionProvider))] +[RegisterSingleton] public partial class CompletionProvider : ICompletionProvider { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs b/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs index 2181e5df1..c89f69571 100644 --- a/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs +++ b/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; +using Injectio.Attributes; using StabilityMatrix.Avalonia.Extensions; -using StabilityMatrix.Core.Attributes; using TextMateSharp.Grammars; using TextMateSharp.Registry; namespace StabilityMatrix.Avalonia.Models.TagCompletion; -[Singleton(typeof(ITokenizerProvider))] +[RegisterSingleton] public class TokenizerProvider : ITokenizerProvider { private readonly Registry registry = new(new RegistryOptions(ThemeName.DarkPlus)); diff --git a/StabilityMatrix.Avalonia/Program.cs b/StabilityMatrix.Avalonia/Program.cs index eab18ea54..b98696dac 100644 --- a/StabilityMatrix.Avalonia/Program.cs +++ b/StabilityMatrix.Avalonia/Program.cs @@ -401,6 +401,7 @@ private static void CurrentDomain_UnhandledException(object sender, UnhandledExc ex.SetSentryMechanism("AppDomain.UnhandledException", handled: false); sentryId = SentrySdk.CaptureException(ex); SentrySdk.FlushAsync().SafeFireAndForget(); + Logger.Warn(ex, "Unhandled {Type}: {Message}", ex.GetType().Name, ex.Message); } else { @@ -414,9 +415,8 @@ private static void CurrentDomain_UnhandledException(object sender, UnhandledExc DataContext = new ExceptionViewModel { Exception = ex, SentryId = sentryId } }; - var mainWindow = lifetime.MainWindow; // We can only show dialog if main window exists, and is visible - if (mainWindow is { PlatformImpl: not null, IsVisible: true }) + if (lifetime.MainWindow is { PlatformImpl: not null, IsVisible: true } mainWindow) { // Configure for dialog mode dialog.ShowAsDialog = true; @@ -454,7 +454,7 @@ private static void CurrentDomain_UnhandledException(object sender, UnhandledExc } [DoesNotReturn] - private static void ExitWithException(Exception exception) + public static void ExitWithException(Exception exception) { if (SentrySdk.IsEnabled) { diff --git a/StabilityMatrix.Avalonia/Services/AccountsService.cs b/StabilityMatrix.Avalonia/Services/AccountsService.cs index f1e951f2e..763d51e41 100644 --- a/StabilityMatrix.Avalonia/Services/AccountsService.cs +++ b/StabilityMatrix.Avalonia/Services/AccountsService.cs @@ -1,9 +1,9 @@ using System; using System.Net; using System.Threading.Tasks; +using Injectio.Attributes; using Microsoft.Extensions.Logging; using StabilityMatrix.Core.Api; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Api; using StabilityMatrix.Core.Models.Api.CivitTRPC; @@ -13,7 +13,7 @@ namespace StabilityMatrix.Avalonia.Services; -[Singleton(typeof(IAccountsService))] +[RegisterSingleton] public class AccountsService : IAccountsService { private readonly ILogger logger; diff --git a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs index 352e8c983..392eb8758 100644 --- a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs +++ b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs @@ -11,13 +11,13 @@ using CommunityToolkit.Mvvm.ComponentModel; using DynamicData; using DynamicData.Binding; +using Injectio.Attributes; using Microsoft.Extensions.Logging; using SkiaSharp; using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Models.TagCompletion; using StabilityMatrix.Core.Api; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Inference; @@ -33,7 +33,7 @@ namespace StabilityMatrix.Avalonia.Services; /// Manager for the current inference client /// Has observable shared properties for shared info like model names /// -[Singleton(typeof(IInferenceClientManager))] +[RegisterSingleton] public partial class InferenceClientManager : ObservableObject, IInferenceClientManager { private readonly ILogger logger; @@ -455,16 +455,18 @@ await Client.GetRequiredNodeOptionNamesFromOptionalNodeAsync("UnetLoaderGGUF", " unetModels = unetModels.Concat(ggufModelNames.Select(HybridModelFile.FromRemote)); } - unetModelsSource.EditDiff(unetModels, HybridModelFile.Comparer); + unetModelsSource.AddOrUpdate(unetModels, HybridModelFile.Comparer); } // Get CLIP model names from DualCLIPLoader node if (await Client.GetNodeOptionNamesAsync("DualCLIPLoader", "clip_name1") is { } clipModelNames) { - clipModelsSource.EditDiff( - clipModelNames.Select(HybridModelFile.FromRemote), - HybridModelFile.Comparer - ); + IEnumerable models = + [ + HybridModelFile.None, + ..clipModelNames.Select(HybridModelFile.FromRemote) + ]; + clipModelsSource.EditDiff(models, HybridModelFile.Comparer); } } diff --git a/StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs b/StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs index 26e948dd7..8a7715848 100644 --- a/StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs +++ b/StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs @@ -5,11 +5,11 @@ using System.Web; using Avalonia.Controls.Notifications; using Avalonia.Threading; +using Injectio.Attributes; using MessagePipe; using Microsoft.Extensions.Logging; using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Core.Api; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Api; @@ -18,7 +18,7 @@ namespace StabilityMatrix.Avalonia.Services; -[Singleton(typeof(IModelDownloadLinkHandler))] +[RegisterSingleton] public class ModelDownloadLinkHandler( IDistributedSubscriber uriHandlerSubscriber, ILogger logger, diff --git a/StabilityMatrix.Avalonia/Services/ModelImportService.cs b/StabilityMatrix.Avalonia/Services/ModelImportService.cs index 452832afd..cadde8122 100644 --- a/StabilityMatrix.Avalonia/Services/ModelImportService.cs +++ b/StabilityMatrix.Avalonia/Services/ModelImportService.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using AsyncAwaitBestPractices; using Avalonia.Controls.Notifications; -using StabilityMatrix.Core.Attributes; +using Injectio.Attributes; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Api; using StabilityMatrix.Core.Models.FileInterfaces; @@ -13,7 +13,7 @@ namespace StabilityMatrix.Avalonia.Services; -[Singleton(typeof(IModelImportService))] +[RegisterSingleton] public class ModelImportService( IDownloadService downloadService, INotificationService notificationService, @@ -161,6 +161,6 @@ public async Task DoImport( // Add hash context action download.ContextAction = CivitPostDownloadContextAction.FromCivitFile(modelFile); - download.Start(); + await trackedDownloadService.TryStartDownload(download); } } diff --git a/StabilityMatrix.Avalonia/Services/NavigationService.cs b/StabilityMatrix.Avalonia/Services/NavigationService.cs index a4c9f50bb..ad3b46105 100644 --- a/StabilityMatrix.Avalonia/Services/NavigationService.cs +++ b/StabilityMatrix.Avalonia/Services/NavigationService.cs @@ -4,28 +4,19 @@ using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Media.Animation; using FluentAvalonia.UI.Navigation; +using Injectio.Attributes; using StabilityMatrix.Avalonia.Animations; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.Services; -[Singleton( - ImplType = typeof(NavigationService), - InterfaceType = typeof(INavigationService) -)] -[Singleton( - ImplType = typeof(NavigationService), - InterfaceType = typeof(INavigationService) -)] -[Singleton( - ImplType = typeof(NavigationService), - InterfaceType = typeof(INavigationService) -)] +[RegisterSingleton, NavigationService>] +[RegisterSingleton, NavigationService>] +[RegisterSingleton, NavigationService>] public class NavigationService : INavigationService { private Frame? _frame; diff --git a/StabilityMatrix.Avalonia/Services/NotificationService.cs b/StabilityMatrix.Avalonia/Services/NotificationService.cs index 2c6b3da28..c65d91f77 100644 --- a/StabilityMatrix.Avalonia/Services/NotificationService.cs +++ b/StabilityMatrix.Avalonia/Services/NotificationService.cs @@ -6,10 +6,10 @@ using Avalonia.Threading; using DesktopNotifications.FreeDesktop; using DesktopNotifications.Windows; +using Injectio.Attributes; using Microsoft.Extensions.Logging; using Nito.AsyncEx; using StabilityMatrix.Avalonia.Extensions; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Exceptions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models; @@ -19,7 +19,7 @@ namespace StabilityMatrix.Avalonia.Services; -[Singleton(typeof(INotificationService))] +[RegisterSingleton] public class NotificationService(ILogger logger, ISettingsManager settingsManager) : INotificationService, IDisposable @@ -225,6 +225,7 @@ public async Task> TryAsync( } catch (Exception e) { + logger.LogError(e, "{Exception}", e); Show(new Notification(title, message ?? e.Message, appearance)); return new TaskResult(false, e); } diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs index 7530f8001..641ff82e1 100644 --- a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs +++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs @@ -6,12 +6,12 @@ using System.Threading.Tasks; using Avalonia.Controls.Notifications; using CommunityToolkit.Mvvm.ComponentModel; +using Injectio.Attributes; using KeyedSemaphores; using Microsoft.Extensions.Logging; using Nito.Disposables.Internals; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.ViewModels; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper.Factory; using StabilityMatrix.Core.Models; @@ -23,7 +23,7 @@ namespace StabilityMatrix.Avalonia.Services; -[Singleton] +[RegisterSingleton] public partial class RunningPackageService( ILogger logger, IPackageFactory packageFactory, diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj index d99372018..133b2d5fd 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj @@ -1,19 +1,13 @@  - - net8.0 - - - net8.0-windows10.0.17763.0 - + + WinExe - win-x64;linux-x64;osx-x64;osx-arm64 - enable true true ./Assets/Icon.ico - 2.12.0-dev.999 + 2.13.0-dev.999 $(Version) true true @@ -78,74 +72,75 @@ - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -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 @@ + + + + + + + + - - + + + + + + + + - - - - + - - + + - - - - + + + + - - + + + IsIndeterminate="True" + IsVisible="{Binding IsLoading}" /> diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml.cs index 197a94ad1..f7e8ddece 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.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.Dialogs; -[Transient] +[RegisterTransient] public partial class PythonPackagesDialog : UserControlBase { public PythonPackagesDialog() diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml.cs index f9eb3bf1a..75b790d50 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml.cs @@ -1,10 +1,10 @@ using Avalonia.Markup.Xaml; +using Injectio.Attributes; using StabilityMatrix.Avalonia.Controls; -using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Views.Dialogs; -[Transient] +[RegisterTransient] public partial class SelectDataDirectoryDialog : UserControlBase { public SelectDataDirectoryDialog() diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml index 317a3a1ea..ccae8532c 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml @@ -283,11 +283,8 @@ Margin="8,8" ExpandDirection="Down" Header="{x:Static lang:Resources.Label_ModelDescription}"> - - + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs index 94ac9c317..349007bc1 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs @@ -1,10 +1,10 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; -using StabilityMatrix.Core.Attributes; +using Injectio.Attributes; namespace StabilityMatrix.Avalonia.Views.Dialogs; -[Transient] +[RegisterTransient] public partial class UpdateDialog : UserControl { public UpdateDialog() diff --git a/StabilityMatrix.Avalonia/Views/FirstLaunchSetupWindow.axaml b/StabilityMatrix.Avalonia/Views/FirstLaunchSetupWindow.axaml index d984fb49b..1ef2f3b70 100644 --- a/StabilityMatrix.Avalonia/Views/FirstLaunchSetupWindow.axaml +++ b/StabilityMatrix.Avalonia/Views/FirstLaunchSetupWindow.axaml @@ -1,29 +1,33 @@ - + - + VerticalAlignment="Center" + Orientation="Vertical"> + @@ -32,73 +36,105 @@ - + + TextWrapping="WrapWithOverflow" /> - - - + + + + + + + + + + Margin="8,0" + HorizontalAlignment="Left" + Fill="{DynamicResource TextFillColorPrimaryBrush}" /> - + + + + + + + + + + + + + - + - - + + - + Text="{x:Static lang:Resources.Label_ReadAndAgree}" /> + + NavigateUri="https://lykos.ai/matrix/license" /> - + diff --git a/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs index 191916d27..4c51ba7ce 100644 --- a/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.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 InstalledWorkflowsPage : UserControlBase { public InstalledWorkflowsPage() diff --git a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs index 4ccd42271..134973f6e 100644 --- a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs @@ -4,15 +4,15 @@ using Avalonia.Interactivity; using Avalonia.Threading; using AvaloniaEdit; +using Injectio.Attributes; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Avalonia.Models; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; namespace StabilityMatrix.Avalonia.Views; -[Singleton] +[RegisterSingleton] public partial class LaunchPageView : UserControlBase { private const int LineOffset = 5; diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs index 12ae3d4b9..b4f429640 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -6,6 +6,7 @@ using System.Linq; using System.Reactive.Linq; using System.Threading; +using System.Threading.Tasks; using AsyncAwaitBestPractices; using AsyncImageLoader; using Avalonia; @@ -14,7 +15,9 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.Media.Immutable; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.Threading; using FluentAvalonia.Styling; @@ -22,16 +25,15 @@ using FluentAvalonia.UI.Media; using FluentAvalonia.UI.Media.Animation; using FluentAvalonia.UI.Windowing; +using Injectio.Attributes; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using StabilityMatrix.Avalonia.Animations; using StabilityMatrix.Avalonia.Controls; -using StabilityMatrix.Avalonia.Extensions; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.Settings; @@ -41,12 +43,13 @@ using TeachingTip = FluentAvalonia.UI.Controls.TeachingTip; #if DEBUG using StabilityMatrix.Avalonia.Diagnostics.Views; +using StabilityMatrix.Avalonia.Extensions; #endif namespace StabilityMatrix.Avalonia.Views; [SuppressMessage("ReSharper", "UnusedParameter.Local")] -[Singleton] +[RegisterSingleton] public partial class MainWindow : AppWindowBase { private readonly INotificationService notificationService; @@ -56,7 +59,7 @@ public partial class MainWindow : AppWindowBase private FlyoutBase? progressFlyout; - [DesignOnly(true)] + /*[DesignOnly(true)] [SuppressMessage("ReSharper", "UnusedMember.Global")] public MainWindow() : this( @@ -70,13 +73,14 @@ public MainWindow() { throw new InvalidOperationException("Design constructor called in non-design mode"); } - } + }*/ public MainWindow( INotificationService notificationService, INavigationService navigationService, ISettingsManager settingsManager, - ILogger logger + ILogger logger, + Lazy lazyViewModel ) { this.notificationService = notificationService; @@ -93,6 +97,63 @@ ILogger logger #endif TitleBar.ExtendsContentIntoTitleBar = true; TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; + ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects + ? ExtendClientAreaChromeHints.NoChrome + : ExtendClientAreaChromeHints.PreferSystemChrome; + + // Load window positions + if ( + settingsManager.Settings.WindowSettings is { } windowSettings + && !Program.Args.ResetWindowPosition + ) + { + Position = new PixelPoint(windowSettings.X, windowSettings.Y); + Width = windowSettings.Width; + Height = windowSettings.Height; + WindowState = windowSettings.IsMaximized ? WindowState.Maximized : WindowState.Normal; + } + else + { + WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + + if (Program.Args.IsSplashScreenEnabled) + { + var appIconStream = Assets.AppIcon.Open(); + var appIcon = new Bitmap(appIconStream); + appIconStream.Dispose(); + + SplashScreen = new ApplicationSplashScreen + { + AppIcon = appIcon, + InitApp = cancellationToken => + { + return Dispatcher + .UIThread.InvokeAsync(() => StartupInitialize(lazyViewModel, cancellationToken)) + .GetTask(); + } + }; + } + else + { + StartupInitialize(lazyViewModel); + } + } + + /// + /// Run startup initialization. + /// This runs on the UI thread. + /// + private void StartupInitialize( + Lazy lazyViewModel, + CancellationToken cancellationToken = default + ) + { + using var _ = CodeTimer.StartDebug(); + + Dispatcher.UIThread.VerifyAccess(); + + cancellationToken.ThrowIfCancellationRequested(); navigationService.TypedNavigation += NavigationService_OnTypedNavigation; @@ -109,7 +170,7 @@ ILogger logger .Where(x => x.EventArgs.PreviousSize != x.EventArgs.NewSize) .Throttle(TimeSpan.FromMilliseconds(100)) .Select(x => x.EventArgs.NewSize) - .ObserveOn(SynchronizationContext.Current) + .ObserveOn(SynchronizationContext.Current!) .Subscribe(newSize => { var validWindowPosition = Screens.All.Any(screen => screen.Bounds.Contains(Position)); @@ -134,7 +195,7 @@ ILogger logger .Where(x => Screens.All.Any(screen => screen.Bounds.Contains(x.EventArgs.Point))) .Throttle(TimeSpan.FromMilliseconds(100)) .Select(x => x.EventArgs.Point) - .ObserveOn(SynchronizationContext.Current) + .ObserveOn(SynchronizationContext.Current!) .Subscribe(position => { settingsManager.Transaction( @@ -151,6 +212,12 @@ ILogger logger ignoreMissingLibraryDir: true ); }); + + using (CodeTimer.StartDebug("Load view model")) + { + var viewModel = lazyViewModel.Value; + DataContext = viewModel; + } } private void InstanceOnDownloadsTeachingTipRequested(object? sender, EventArgs e) diff --git a/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs index b6a615f34..002ece52c 100644 --- a/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs @@ -1,14 +1,14 @@ using System; using AsyncAwaitBestPractices; using Avalonia.Controls; +using Injectio.Attributes; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Models; -using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.Views; -[Singleton] +[RegisterSingleton] public partial class OpenArtBrowserPage : UserControlBase { private readonly ISettingsManager settingsManager; diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs index 023eae702..4ca7137ac 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs @@ -1,11 +1,11 @@ using Avalonia.Input; +using Injectio.Attributes; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.ViewModels; -using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Views; -[Singleton] +[RegisterSingleton] public partial class OutputsPage : UserControlBase { public OutputsPage() diff --git a/StabilityMatrix.Avalonia/Views/PackageManager/MainPackageManagerView.axaml b/StabilityMatrix.Avalonia/Views/PackageManager/MainPackageManagerView.axaml index 183596f9d..1c18d27e7 100644 --- a/StabilityMatrix.Avalonia/Views/PackageManager/MainPackageManagerView.axaml +++ b/StabilityMatrix.Avalonia/Views/PackageManager/MainPackageManagerView.axaml @@ -1,21 +1,24 @@ - + @@ -23,17 +26,20 @@ - - - - - + + + + + - + @@ -44,67 +50,70 @@ + Background="#202020" + CornerRadius="8" + IsVisible="{Binding IsUnknownPackage}"> + Text="{x:Static lang:Resources.Label_UnknownPackage}" + TextAlignment="Center" + TextWrapping="Wrap" /> - - - + + + - - - - + - - - - + + + + - + + IsIconVisible="False" + IsOpen="{Binding !!SelectedAvailableItems.Count}"> - + - + - + RowSpacing="12"> + + Classes="search" + DataContext="{Binding InstalledItemsSearchCollection}" + Text="{Binding Query, Mode=TwoWay}" + Watermark="{x:Static lang:Resources.Action_Search}" /> - + - + - + - + + IsIconVisible="False" + IsOpen="{Binding !!SelectedInstalledItems.Count}"> @@ -334,7 +463,28 @@ Classes="accent" Command="{Binding UpdateSelectedExtensionsCommand}" Content="{x:Static lang:Resources.Action_Update}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -151,8 +164,8 @@ @@ -177,7 +190,7 @@ - + - - + + @@ -207,8 +222,10 @@ @@ -219,9 +236,9 @@ Grid.Row="0" Grid.Column="1" Margin="8,0" - ToolTip.Tip="Show Progress Dialog" + Classes="transparent-full" Command="{Binding ShowProgressDialog}" - Classes="transparent-full"> + ToolTip.Tip="Show Progress Dialog"> diff --git a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml.cs b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml.cs index c96b7f03a..c713065f5 100644 --- a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml.cs @@ -1,10 +1,10 @@ using Avalonia.Markup.Xaml; +using Injectio.Attributes; using StabilityMatrix.Avalonia.Controls; -using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Views; -[Singleton] +[RegisterSingleton] public partial class ProgressManagerPage : UserControlBase { public ProgressManagerPage() diff --git a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs index 0dbc0f50c..ae3d43295 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.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.Settings; -[Singleton] +[RegisterSingleton] public partial class AccountSettingsPage : UserControlBase { public AccountSettingsPage() diff --git a/StabilityMatrix.Avalonia/Views/Settings/AnalyticsSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/AnalyticsSettingsPage.axaml.cs index 7aad835d8..2878718d7 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/AnalyticsSettingsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Settings/AnalyticsSettingsPage.axaml.cs @@ -1,10 +1,10 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; -using StabilityMatrix.Core.Attributes; +using Injectio.Attributes; namespace StabilityMatrix.Avalonia.Views.Settings; -[Transient] +[RegisterTransient] public partial class AnalyticsSettingsPage : UserControl { public AnalyticsSettingsPage() diff --git a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml index f326cd1cc..28d4f3c03 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml @@ -9,12 +9,12 @@ xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" + xmlns:native="clr-namespace:StabilityMatrix.Native;assembly=StabilityMatrix.Native" xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" xmlns:system="clr-namespace:System;assembly=System.Runtime" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings" - xmlns:native="clr-namespace:StabilityMatrix.Native;assembly=StabilityMatrix.Native" d:DataContext="{x:Static mocks:DesignData.InferenceSettingsViewModel}" d:DesignHeight="650" d:DesignWidth="900" @@ -103,12 +103,12 @@ - - - + + + @@ -116,7 +116,7 @@ - + @@ -151,8 +151,6 @@ PreferredPlacement="Top" Target="{Binding #OutputImageFileNameFormatTextBox, Mode=OneWay}"> - diff --git a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs index eea11dedf..60cee167e 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.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.Settings; -[Singleton] +[RegisterSingleton] public partial class InferenceSettingsPage : UserControlBase { public InferenceSettingsPage() diff --git a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml index 7242ff343..4ed3d7089 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml @@ -376,7 +376,7 @@ - + - + + + + + + + + + + @@ -482,9 +502,39 @@ + + + + + + + + + + + + + + + @@ -510,9 +560,25 @@ + + + + + + + + + + + + + - + -