Browse Source

Merge branch 'inference-layer-diffuse' of https://github.com/ionite34/StabilityMatrix into inference-layer-diffuse

pull/629/head
Ionite 8 months ago
parent
commit
ae2c9b2783
No known key found for this signature in database
  1. 11
      .github/workflows/release.yml
  2. 2
      Avalonia.Gif/Avalonia.Gif.csproj
  3. 38
      CHANGELOG.md
  4. 52
      README.md
  5. 6
      StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
  6. 7
      StabilityMatrix.Avalonia/App.axaml
  7. 16
      StabilityMatrix.Avalonia/App.axaml.cs
  8. 14
      StabilityMatrix.Avalonia/Assets/sitecustomize.py
  9. 208
      StabilityMatrix.Avalonia/Controls/BetterContextDragBehavior.cs
  10. 6
      StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml
  11. 8
      StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml
  12. 88
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  13. 2
      StabilityMatrix.Avalonia/Helpers/UriHandler.cs
  14. 126
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  15. 42
      StabilityMatrix.Avalonia/Languages/Resources.resx
  16. 3
      StabilityMatrix.Avalonia/Models/ImageSource.cs
  17. 10
      StabilityMatrix.Avalonia/Models/OpenArtCustomNode.cs
  18. 22
      StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
  19. 21
      StabilityMatrix.Avalonia/Models/PackageSteps/UnpackSiteCustomizeStep.cs
  20. 2
      StabilityMatrix.Avalonia/Services/INavigationService.cs
  21. 2
      StabilityMatrix.Avalonia/Services/NavigationService.cs
  22. 40
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  23. 29
      StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
  24. 25
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs
  25. 21
      StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
  26. 2
      StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
  27. 185
      StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs
  28. 2
      StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
  29. 6
      StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs
  30. 173
      StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
  31. 348
      StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
  32. 12
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  33. 6
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  34. 18
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
  35. 13
      StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs
  36. 10
      StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs
  37. 10
      StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
  38. 39
      StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs
  39. 6
      StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
  40. 19
      StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml
  41. 10
      StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml
  42. 23
      StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
  43. 136
      StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml
  44. 13
      StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml.cs
  45. 254
      StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
  46. 13
      StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs
  47. 4
      StabilityMatrix.Avalonia/Views/MainWindow.axaml
  48. 5
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
  49. 360
      StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
  50. 41
      StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs
  51. 4
      StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallBrowserView.axaml
  52. 59
      StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml
  53. 6
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml
  54. 200
      StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
  55. 12
      StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml
  56. 13
      StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml.cs
  57. 17
      StabilityMatrix.Core/Api/IOpenArtApi.cs
  58. 11
      StabilityMatrix.Core/Extensions/ProgressExtensions.cs
  59. 3
      StabilityMatrix.Core/Helper/EventManager.cs
  60. 23
      StabilityMatrix.Core/Helper/Utilities.cs
  61. 4
      StabilityMatrix.Core/Models/Api/CivitImage.cs
  62. 15
      StabilityMatrix.Core/Models/Api/OpenArt/NodesCount.cs
  63. 24
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtCreator.cs
  64. 14
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDateTime.cs
  65. 15
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadRequest.cs
  66. 12
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadResponse.cs
  67. 21
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtFeedRequest.cs
  68. 18
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchRequest.cs
  69. 15
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResponse.cs
  70. 33
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResult.cs
  71. 33
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtStats.cs
  72. 15
      StabilityMatrix.Core/Models/Api/OpenArt/OpenArtThumbnail.cs
  73. 8420
      StabilityMatrix.Core/Models/ComfyNodeMap.cs
  74. 36
      StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs
  75. 2
      StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs
  76. 15
      StabilityMatrix.Core/Models/PackageModification/SetupOutputSharingStep.cs
  77. 9
      StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs
  78. 5
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  79. 8
      StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs
  80. 57
      StabilityMatrix.Core/Models/Packages/InvokeAI.cs
  81. 23
      StabilityMatrix.Core/Models/Packages/KohyaSs.cs
  82. 8
      StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
  83. 46
      StabilityMatrix.Core/Models/Progress/ProgressReport.cs
  84. 14
      StabilityMatrix.Core/Models/Settings/Settings.cs
  85. 2
      StabilityMatrix.Core/Python/PyVenvRunner.cs
  86. 1
      StabilityMatrix.Core/Services/ISettingsManager.cs
  87. 1
      StabilityMatrix.Core/Services/SettingsManager.cs
  88. 12
      StabilityMatrix.Core/StabilityMatrix.Core.csproj
  89. 8
      StabilityMatrix.Tests/StabilityMatrix.Tests.csproj
  90. 8
      StabilityMatrix.UITests/StabilityMatrix.UITests.csproj

11
.github/workflows/release.yml

@ -76,20 +76,15 @@ jobs:
echo "Using version ${{ github.event.inputs.version }}"
echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Set up .NET 6 (for PupNet)
- name: Set up .NET 8
uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
dotnet-version: '8.0.x'
- name: Install PupNet
run: |
sudo apt-get -y install libfuse2
dotnet tool install --framework net6.0 -g KuiperZone.PupNet
- name: Set up .NET 8
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
dotnet tool install -g KuiperZone.PupNet
- name: PupNet Build
env:

2
Avalonia.Gif/Avalonia.Gif.csproj

@ -10,7 +10,7 @@
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.9" />
<PackageReference Include="Avalonia" Version="11.0.10" />
<PackageReference Include="SkiaSharp" Version="2.88.7" />
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
</ItemGroup>

38
CHANGELOG.md

@ -5,6 +5,32 @@ 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.10.0-pre.2
### Added
- Added more metadata to the image dialog info flyout
- Added Restart button to console page
### Changed
- Model download location selector now searches all subfolders
### Fixed
- Fixed Civitai model browser not showing images when "Show NSFW" is disabled
- Fixed crash when Installed Workflows page is opened with no Workflows folder
- Fixed progress bars not displaying properly during package installs & updates
## v2.10.0-pre.1
### Added
- Added OpenArt.AI workflow browser for ComfyUI workflows
- Added Output Sharing toggle in Advanced Options during install flow
### Changed
- Changed to a new image control for pages with many images
- Removed Symlink option for InvokeAI due to changes with InvokeAI v4.0+
- Output sharing is now enabled by default for new installations
- (Internal) Updated to Avalonia 11.0.10
### Fixed
- Improved performance when deleting many images from the Outputs page
- Fixed ComfyUI torch downgrading to 2.1.2 when updating
- Fixed [#529](https://github.com/LykosAI/StabilityMatrix/issues/529) - OneTrainer requesting input during update
- Fixed "Could not find entry point for InvokeAI" error on InvokeAI v4.0+
## v2.10.0-dev.3
### Added
- Added support for deep links from the new Stability Matrix Chrome extension
@ -44,6 +70,17 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
### Removed
- Removed the main Launch page, as it is no longer needed with the new Packages page
## v2.9.2
### Changed
- Due to changes with the CivitAI API, you can no longer select a specific page in the CivitAI Model Browser
- Due to the above API changes, new pages are now loaded via "infinite scrolling"
### Fixed
- Fixed models not being removed from the installed models cache when deleting them from the Checkpoints page
- Fixed model download location options for VAEs in the CivitAI Model Browser
- Fixed One-Click install progress dialog not disappearing after completion
- Fixed ComfyUI with Inference pop-up during one-click install appearing below the visible scroll area
- Fixed no packages being available for one-click install on PCs without a GPU
## v2.9.1
### Added
- Fixed [#498](https://github.com/LykosAI/StabilityMatrix/issues/498) Added "Pony" category to CivitAI Model Browser
@ -100,6 +137,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Added copy image support on linux and macOS for Inference outputs viewer menu
### Fixed
- Fixed StableSwarmUI not installing properly on macOS
- Fixed output sharing for Stable Diffusion WebUI Forge
- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS
- Fixed default command line args for SDWebUI Forge on macOS
- Fixed output paths and output sharing for SDWebUI Forge

52
README.md

@ -2,6 +2,7 @@
[![Build](https://github.com/LykosAI/StabilityMatrix/actions/workflows/build.yml/badge.svg)](https://github.com/LykosAI/StabilityMatrix/actions/workflows/build.yml)
[![Discord Server](https://img.shields.io/discord/1115555685476868168?logo=discord&logoColor=white&label=Discord%20Server)](https://discord.com/invite/TUrgfECxHz)
[![Release](https://img.shields.io/github/v/release/LykosAI/StabilityMatrix?label=Latest%20Release&link=https%3A%2F%2Fgithub.com%2FLykosAI%2FStabilityMatrix%2Freleases%2Flatest)][release]
[release]: https://github.com/LykosAI/StabilityMatrix/releases/latest
[download-win-x64]: https://github.com/LykosAI/StabilityMatrix/releases/latest/download/StabilityMatrix-win-x64.zip
@ -9,7 +10,7 @@
[download-macos-arm64]: https://github.com/LykosAI/StabilityMatrix/releases/latest/download/StabilityMatrix-macos-arm64.dmg
[auto1111]: https://github.com/AUTOMATIC1111/stable-diffusion-webui
[auto1111-directml]: https://github.com/lshqqytiger/stable-diffusion-webui-directml
[sdwebui-directml]: https://github.com/lshqqytiger/stable-diffusion-webui-directml
[webui-ux]: https://github.com/anapnoe/stable-diffusion-webui-ux
[comfy]: https://github.com/comfyanonymous/ComfyUI
[sdnext]: https://github.com/vladmandic/automatic
@ -21,48 +22,36 @@
[fooocus-controlnet]: https://github.com/fenneishi/Fooocus-ControlNet-SDXL
[kohya-ss]: https://github.com/bmaltais/kohya_ss
[onetrainer]: https://github.com/Nerogar/OneTrainer
[forge]: https://github.com/lllyasviel/stable-diffusion-webui-forge
[stable-swarm]: https://github.com/Stability-AI/StableSwarmUI
[civitai]: https://civitai.com/
[huggingface]: https://huggingface.co/
Multi-Platform Package Manager and Inference UI for Stable Diffusion
![Header image for Stability Matrix, Multi-Platform Package Manager and Inference UI for Stable Diffusion](https://cdn.lykos.ai/static/sm-banner-rounded.webp)
### ✨ New in 2.5 - [Inference](#inference-A-reimagined-built-in-Stable-Diffusion-experience), a built-in interface for Stable Diffusion powered by ComfyUI
[![Windows](https://img.shields.io/badge/Windows%2010,%2011-%230079d5.svg?style=for-the-badge&logo=Windows%2011&logoColor=white)][download-win-x64]
[![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)][download-linux-x64]
[![macOS](https://img.shields.io/badge/mac%20os%20%28apple%20silicon%29-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)][download-macos-arm64]
Multi-Platform Package Manager and Inference UI for Stable Diffusion
### 🖱 One click install and update for Stable Diffusion Web UI Packages
- Supports:
- [Automatic 1111][auto1111], [Automatic 1111 DirectML][auto1111-directml], [SD Web UI-UX][webui-ux], [SD.Next][sdnext]
- [Stable Diffusion WebUI Forge][forge], [Automatic 1111][auto1111], [Automatic 1111 DirectML][auto1111-directml], [SD Web UI-UX][webui-ux], [SD.Next][sdnext]
- [Fooocus][fooocus], [Fooocus MRE][fooocus-mre], [Fooocus ControlNet SDXL][fooocus-controlnet], [Ruined Fooocus][ruined-fooocus]
- [ComfyUI][comfy]
- [StableSwarmUI][stable-swarm]
- [VoltaML][voltaml]
- [InvokeAI][invokeai]
- [Kohya's GUI][kohya-ss]
- [OneTrainer][onetrainer]
- Manage plugins / extensions for supported packages ([Automatic 1111][auto1111], [Comfy UI][comfy])
- Manage plugins / extensions for supported packages ([Automatic1111][auto1111], [Comfy UI][comfy], [SD Web UI-UX][webui-ux], and [SD.Next][sdnext])
- Easily install or update Python dependencies for each package
- Embedded Git and Python dependencies, with no need for either to be globally installed
- Fully portable - move Stability Matrix's Data Directory to a new drive or computer at any time
### 🚀 Launcher with syntax highlighted terminal emulator, routed GUI input prompts
- Launch arguments editor with predefined or custom options for each Package install
- Configurable Environment Variables
### 🗃 Checkpoint Manager, configured to be shared by all Package installs
- Option to find CivitAI metadata and preview thumbnails for new local imports
### ☁ Model Browser to import from [CivitAI][civitai] and [HuggingFace][huggingface]
- Automatically imports to the associated model folder depending on the model type
- Downloads relevant metadata files and preview image
![header](https://cdn.lykos.ai/static/sm-banner-rounded.webp)
[![Release](https://img.shields.io/github/v/release/LykosAI/StabilityMatrix?label=Latest%20Release&link=https%3A%2F%2Fgithub.com%2FLykosAI%2FStabilityMatrix%2Freleases%2Flatest)][release]
[![Windows](https://img.shields.io/badge/Windows%2010,%2011-%230079d5.svg?style=for-the-badge&logo=Windows%2011&logoColor=white)][download-win-x64]
[![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)][download-linux-x64]
[![macOS](https://img.shields.io/badge/mac%20os%20%28apple%20silicon%29-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)][download-macos-arm64]
### Inference - A reimagined built-in Stable Diffusion experience
### ✨ Inference - A Reimagined Interface for Stable Diffusion, Built-In to Stability Matrix
- Powerful auto-completion and syntax highlighting using a formal language grammar
- Workspaces open in tabs that save and load from `.smproj` project files
@ -76,13 +65,20 @@ Multi-Platform Package Manager and Inference UI for Stable Diffusion
<img style="width: 80%; height: 80%" src="https://github.com/LykosAI/StabilityMatrix/assets/13956642/4341cc34-a584-4e9c-bb3b-276009bdae80" alt=""/>
</p>
### Searchable launch options
### 🚀 Launcher with syntax highlighted terminal emulator, routed GUI input prompts
- Launch arguments editor with predefined or custom options for each Package install
- Configurable Environment Variables
<p align="center">
<img style="width: 80%; height: 80%" src="https://github.com/LykosAI/StabilityMatrix/assets/13956642/75456866-9d95-47c6-8c0a-fdc19443ee02" alt=""/>
</p>
### Model browser powered by [Civit AI][civitai]
- Downloads new models, automatically uses the appropriate shared model directory
### 🗃 Checkpoint Manager, configured to be shared by all Package installs
- Option to find CivitAI metadata and preview thumbnails for new local imports
### ☁ Model Browser to import from [CivitAI][civitai] and [HuggingFace][huggingface]
- Automatically imports to the associated model folder depending on the model type
- Downloads relevant metadata files and preview image
- Pause and resume downloads, even after closing the app
<p align="center">

6
StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj

@ -19,12 +19,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.9" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.9" />
<PackageReference Include="Avalonia" Version="11.0.10" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="NLog" Version="5.2.8" />
</ItemGroup>

7
StabilityMatrix.Avalonia/App.axaml

@ -4,7 +4,7 @@
xmlns:local="using:StabilityMatrix.Avalonia"
xmlns:idcr="using:Dock.Avalonia.Controls.Recycling"
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
xmlns:controls="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
Name="Stability Matrix"
RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
@ -82,10 +82,13 @@
<StyleInclude Source="Controls/Inference/ControlNetCard.axaml"/>
<StyleInclude Source="Controls/Inference/PromptExpansionCard.axaml"/>
<StyleInclude Source="Controls/Inference/LayerDiffuseCard.axaml"/>
<controls:ControlThemes/>
<labs:ControlThemes/>
<Style Selector="DockControl">
<Setter Property="(DockProperties.ControlRecycling)" Value="{StaticResource ControlRecyclingKey}" />
</Style>
<Style Selector="labs|AsyncImage Border">
<Setter Property="ClipToBounds" Value="True"/>
</Style>
</Application.Styles>
</Application>

16
StabilityMatrix.Avalonia/App.axaml.cs

@ -330,7 +330,8 @@ public sealed class App : Application
provider.GetRequiredService<InferenceViewModel>(),
provider.GetRequiredService<CheckpointsPageViewModel>(),
provider.GetRequiredService<CheckpointBrowserViewModel>(),
provider.GetRequiredService<OutputsPageViewModel>()
provider.GetRequiredService<OutputsPageViewModel>(),
provider.GetRequiredService<WorkflowsPageViewModel>()
},
FooterPages = { provider.GetRequiredService<SettingsViewModel>() }
}
@ -553,7 +554,7 @@ public sealed class App : Application
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("https://civitai.com");
c.Timeout = TimeSpan.FromSeconds(15);
c.Timeout = TimeSpan.FromSeconds(30);
})
.AddPolicyHandler(retryPolicy);
@ -562,7 +563,7 @@ public sealed class App : Application
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("https://civitai.com");
c.Timeout = TimeSpan.FromSeconds(15);
c.Timeout = TimeSpan.FromSeconds(30);
})
.AddPolicyHandler(retryPolicy);
@ -580,6 +581,15 @@ public sealed class App : Application
new TokenAuthHeaderHandler(serviceProvider.GetRequiredService<LykosAuthTokenProvider>())
);
services
.AddRefitClient<IOpenArtApi>(defaultRefitSettings)
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("https://openart.ai/api/public/workflows");
c.Timeout = TimeSpan.FromSeconds(30);
})
.AddPolicyHandler(retryPolicy);
// Add Refit client managers
services.AddHttpClient("A3Client").AddPolicyHandler(localTimeout.WrapAsync(localRetryPolicy));

14
StabilityMatrix.Avalonia/Assets/sitecustomize.py

@ -69,6 +69,20 @@ def _patch_rich_console():
except Exception as e:
print("[sitecustomize error]:", e)
try:
from pip._vendor.rich import console
class _Console(console.Console):
@property
def is_terminal(self) -> bool:
return True
console.Console = _Console
except ImportError:
pass
except Exception as e:
print("[sitecustomize error]:", e)
_patch_rich_console()
# Patch tqdm to use stdout instead of stderr

208
StabilityMatrix.Avalonia/Controls/BetterContextDragBehavior.cs

@ -0,0 +1,208 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactions.DragAndDrop;
using Avalonia.Xaml.Interactivity;
namespace StabilityMatrix.Avalonia.Controls;
public class BetterContextDragBehavior : Behavior<Control>
{
private Point _dragStartPoint;
private PointerEventArgs? _triggerEvent;
private bool _lock;
private bool _captured;
public static readonly StyledProperty<object?> ContextProperty = AvaloniaProperty.Register<
ContextDragBehavior,
object?
>(nameof(Context));
public static readonly StyledProperty<IDragHandler?> HandlerProperty = AvaloniaProperty.Register<
ContextDragBehavior,
IDragHandler?
>(nameof(Handler));
public static readonly StyledProperty<double> HorizontalDragThresholdProperty = AvaloniaProperty.Register<
ContextDragBehavior,
double
>(nameof(HorizontalDragThreshold), 3);
public static readonly StyledProperty<double> VerticalDragThresholdProperty = AvaloniaProperty.Register<
ContextDragBehavior,
double
>(nameof(VerticalDragThreshold), 3);
public static readonly StyledProperty<string> DataFormatProperty = AvaloniaProperty.Register<
BetterContextDragBehavior,
string
>("DataFormat");
public string DataFormat
{
get => GetValue(DataFormatProperty);
set => SetValue(DataFormatProperty, value);
}
public object? Context
{
get => GetValue(ContextProperty);
set => SetValue(ContextProperty, value);
}
public IDragHandler? Handler
{
get => GetValue(HandlerProperty);
set => SetValue(HandlerProperty, value);
}
public double HorizontalDragThreshold
{
get => GetValue(HorizontalDragThresholdProperty);
set => SetValue(HorizontalDragThresholdProperty, value);
}
public double VerticalDragThreshold
{
get => GetValue(VerticalDragThresholdProperty);
set => SetValue(VerticalDragThresholdProperty, value);
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree()
{
AssociatedObject?.AddHandler(
InputElement.PointerPressedEvent,
AssociatedObject_PointerPressed,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
AssociatedObject?.AddHandler(
InputElement.PointerReleasedEvent,
AssociatedObject_PointerReleased,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
AssociatedObject?.AddHandler(
InputElement.PointerMovedEvent,
AssociatedObject_PointerMoved,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
AssociatedObject?.AddHandler(
InputElement.PointerCaptureLostEvent,
AssociatedObject_CaptureLost,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree()
{
AssociatedObject?.RemoveHandler(InputElement.PointerPressedEvent, AssociatedObject_PointerPressed);
AssociatedObject?.RemoveHandler(InputElement.PointerReleasedEvent, AssociatedObject_PointerReleased);
AssociatedObject?.RemoveHandler(InputElement.PointerMovedEvent, AssociatedObject_PointerMoved);
AssociatedObject?.RemoveHandler(InputElement.PointerCaptureLostEvent, AssociatedObject_CaptureLost);
}
private async Task DoDragDrop(PointerEventArgs triggerEvent, object? value)
{
var data = new DataObject();
data.Set(DataFormat, value!);
var effect = DragDropEffects.None;
if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Alt))
{
effect |= DragDropEffects.Link;
}
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Shift))
{
effect |= DragDropEffects.Move;
}
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Control))
{
effect |= DragDropEffects.Copy;
}
else
{
effect |= DragDropEffects.Move;
}
await DragDrop.DoDragDrop(triggerEvent, data, effect);
}
private void Released()
{
_triggerEvent = null;
_lock = false;
}
private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e)
{
var properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (properties.IsLeftButtonPressed)
{
if (e.Source is Control control && AssociatedObject?.DataContext == control.DataContext)
{
_dragStartPoint = e.GetPosition(null);
_triggerEvent = e;
_lock = true;
_captured = true;
}
}
}
private void AssociatedObject_PointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (_captured)
{
if (e.InitialPressMouseButton == MouseButton.Left && _triggerEvent is { })
{
Released();
}
_captured = false;
}
}
private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e)
{
var properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (_captured && properties.IsLeftButtonPressed && _triggerEvent is { })
{
var point = e.GetPosition(null);
var diff = _dragStartPoint - point;
var horizontalDragThreshold = HorizontalDragThreshold;
var verticalDragThreshold = VerticalDragThreshold;
if (Math.Abs(diff.X) > horizontalDragThreshold || Math.Abs(diff.Y) > verticalDragThreshold)
{
if (_lock)
{
_lock = false;
}
else
{
return;
}
var context = Context ?? AssociatedObject?.DataContext;
Handler?.BeforeDragDrop(sender, _triggerEvent, context);
await DoDragDrop(_triggerEvent, context);
Handler?.AfterDragDrop(sender, _triggerEvent, context);
_triggerEvent = null;
}
}
}
private void AssociatedObject_CaptureLost(object? sender, PointerCaptureLostEventArgs e)
{
Released();
_captured = false;
}
}

6
StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml

@ -9,6 +9,7 @@
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"
x:DataType="vmInference:ImageFolderCardViewModel">
<Design.PreviewWith>
@ -195,12 +196,11 @@
<Border ClipToBounds="True" CornerRadius="8">
<Grid RowDefinitions="*,Auto">
<controls:BetterAdvancedImage
<labs:AsyncImage
Width="{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Width}"
Height="{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Height}"
Source="{Binding AbsolutePath}"
Stretch="UniformToFill"
StretchDirection="Both" />
Stretch="UniformToFill" />
<Border
Grid.Row="1"

8
StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml

@ -5,6 +5,7 @@
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
x:DataType="vmInference:ImageGalleryCardViewModel">
<Design.PreviewWith>
@ -115,12 +116,11 @@
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type models:ImageSource}">
<controls:BetterAdvancedImage
<labs:AsyncImage
MaxHeight="65"
CornerRadius="4"
Source="{Binding}"
Stretch="Uniform"
StretchDirection="Both" />
Source="{Binding Uri}"
Stretch="Uniform" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

88
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -36,6 +36,7 @@ using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Api.Comfy;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification;
@ -51,6 +52,7 @@ using HuggingFacePageViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointB
namespace StabilityMatrix.Avalonia.DesignData;
[Localizable(false)]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public static class DesignData
{
@ -358,7 +360,7 @@ public static class DesignData
{
new()
{
Nsfw = "None",
NsfwLevel = 1,
Url =
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/"
+ "78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg"
@ -924,6 +926,36 @@ The gallery images are often inpainted, but you will get something very similar
public static LayerDiffuseCardViewModel LayerDiffuseCardViewModel =>
DialogFactory.Get<LayerDiffuseCardViewModel>();
public static InstalledWorkflowsViewModel InstalledWorkflowsViewModel
{
get
{
var vm = Services.GetRequiredService<InstalledWorkflowsViewModel>();
vm.DisplayedWorkflows = new ObservableCollectionExtended<OpenArtMetadata>
{
new()
{
Workflow = new()
{
Name = "Test Workflow",
Creator = new OpenArtCreator { Name = "Test Creator" },
Thumbnails =
[
new OpenArtThumbnail
{
Url = new Uri(
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512"
)
}
]
}
}
};
return vm;
}
}
public static IList<ICompletionData> SampleCompletionData =>
new List<ICompletionData>
{
@ -1035,6 +1067,60 @@ The gallery images are often inpainted, but you will get something very similar
public static ControlNetCardViewModel ControlNetCardViewModel =>
DialogFactory.Get<ControlNetCardViewModel>();
public static OpenArtWorkflowViewModel OpenArtWorkflowViewModel =>
new(Services.GetRequiredService<ISettingsManager>(), Services.GetRequiredService<IPackageFactory>())
{
Workflow = new OpenArtSearchResult
{
Name = "Test Workflow",
Creator = new OpenArtCreator
{
Name = "Test Creator Name",
Username = "Test Creator Username"
},
Thumbnails =
[
new OpenArtThumbnail
{
Url = new Uri(
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500"
)
}
],
NodesIndex =
[
"Anything Everywhere",
"Reroute",
"Note",
".",
"ComfyUI's ControlNet Auxiliary Preprocessors",
"DWPreprocessor",
"PixelPerfectResolution",
"AIO_Preprocessor",
",",
"ComfyUI",
"PreviewImage",
"CLIPTextEncode",
"EmptyLatentImage",
"SplitImageWithAlpha",
"ControlNetApplyAdvanced",
"JoinImageWithAlpha",
"LatentUpscaleBy",
"VAEEncode",
"LoadImage",
"ControlNetLoader",
"CLIPVisionLoader",
"SaveImage",
",",
"ComfyUI Impact Pack",
"SAMLoader",
"UltralyticsDetectorProvider",
"FaceDetailer",
","
]
}
};
public static string CurrentDirectory => Directory.GetCurrentDirectory();
public static Indexer Types { get; } = new();

2
StabilityMatrix.Avalonia/Helpers/UriHandler.cs

@ -83,7 +83,7 @@ public class UriHandler
[SupportedOSPlatform("windows")]
private void RegisterUriSchemeWin()
{
using var key = Registry.CurrentUser.CreateSubKey(@$"SOFTWARE\Classes\{Scheme}");
using var key = Registry.CurrentUser.CreateSubKey(@$"SOFTWARE\Classes\{Scheme}", true);
key.SetValue("", "URL:" + Description);
key.SetValue(null, Description);

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

@ -374,6 +374,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Open on OpenArt.
/// </summary>
public static string Action_OpenOnOpenArt {
get {
return ResourceManager.GetString("Action_OpenOnOpenArt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open Project....
/// </summary>
@ -1310,6 +1319,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Error retrieving workflows.
/// </summary>
public static string Label_ErrorRetrievingWorkflows {
get {
return ResourceManager.GetString("Label_ErrorRetrievingWorkflows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Everything looks good!.
/// </summary>
@ -1346,6 +1364,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Finished importing workflow and custom nodes.
/// </summary>
public static string Label_FinishedImportingWorkflow {
get {
return ResourceManager.GetString("Label_FinishedImportingWorkflow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to First Page.
/// </summary>
@ -1490,6 +1517,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Infinite Scrolling.
/// </summary>
public static string Label_InfiniteScrolling {
get {
return ResourceManager.GetString("Label_InfiniteScrolling", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Inner exception.
/// </summary>
@ -1778,6 +1814,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Node Details.
/// </summary>
public static string Label_NodeDetails {
get {
return ResourceManager.GetString("Label_NodeDetails", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No extensions found..
/// </summary>
@ -1841,6 +1886,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to OpenArt Browser.
/// </summary>
public static string Label_OpenArtBrowser {
get {
return ResourceManager.GetString("Label_OpenArtBrowser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Output Folder.
/// </summary>
@ -2561,6 +2615,69 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Workflow Browser.
/// </summary>
public static string Label_WorkflowBrowser {
get {
return ResourceManager.GetString("Label_WorkflowBrowser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Workflow Deleted.
/// </summary>
public static string Label_WorkflowDeleted {
get {
return ResourceManager.GetString("Label_WorkflowDeleted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} deleted successfully.
/// </summary>
public static string Label_WorkflowDeletedSuccessfully {
get {
return ResourceManager.GetString("Label_WorkflowDeletedSuccessfully", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Workflow Description.
/// </summary>
public static string Label_WorkflowDescription {
get {
return ResourceManager.GetString("Label_WorkflowDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The workflow and custom nodes have been imported..
/// </summary>
public static string Label_WorkflowImportComplete {
get {
return ResourceManager.GetString("Label_WorkflowImportComplete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Workflow Imported.
/// </summary>
public static string Label_WorkflowImported {
get {
return ResourceManager.GetString("Label_WorkflowImported", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Workflows.
/// </summary>
public static string Label_Workflows {
get {
return ResourceManager.GetString("Label_Workflows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You&apos;re up to date.
/// </summary>
@ -2642,6 +2759,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Installed Workflows.
/// </summary>
public static string TabLabel_InstalledWorkflows {
get {
return ResourceManager.GetString("TabLabel_InstalledWorkflows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add a package to get started!.
/// </summary>

42
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -972,9 +972,30 @@
<data name="Label_SelectDownloadLocation" xml:space="preserve">
<value>Select Download Location:</value>
</data>
<data name="Label_Workflows" xml:space="preserve">
<value>Workflows</value>
</data>
<data name="Label_InfiniteScrolling" xml:space="preserve">
<value>Infinite Scrolling</value>
</data>
<data name="Label_WorkflowBrowser" xml:space="preserve">
<value>Workflow Browser</value>
</data>
<data name="Label_Config" xml:space="preserve">
<value>Config</value>
</data>
<data name="Action_OpenOnOpenArt" xml:space="preserve">
<value>Open on OpenArt</value>
</data>
<data name="Label_NodeDetails" xml:space="preserve">
<value>Node Details</value>
</data>
<data name="Label_WorkflowDescription" xml:space="preserve">
<value>Workflow Description</value>
</data>
<data name="Label_OpenArtBrowser" xml:space="preserve">
<value>OpenArt Browser</value>
</data>
<data name="Action_PreviewPreprocessor" xml:space="preserve">
<value>Preview Preprocessor</value>
</data>
@ -1026,4 +1047,25 @@
<data name="Label_StabilityMatrixAlreadyRunning" xml:space="preserve">
<value>Stability Matrix is already running</value>
</data>
<data name="Label_WorkflowDeletedSuccessfully" xml:space="preserve">
<value>{0} deleted successfully</value>
</data>
<data name="Label_WorkflowDeleted" xml:space="preserve">
<value>Workflow Deleted</value>
</data>
<data name="Label_ErrorRetrievingWorkflows" xml:space="preserve">
<value>Error retrieving workflows</value>
</data>
<data name="TabLabel_InstalledWorkflows" xml:space="preserve">
<value>Installed Workflows</value>
</data>
<data name="Label_WorkflowImported" xml:space="preserve">
<value>Workflow Imported</value>
</data>
<data name="Label_FinishedImportingWorkflow" xml:space="preserve">
<value>Finished importing workflow and custom nodes</value>
</data>
<data name="Label_WorkflowImportComplete" xml:space="preserve">
<value>The workflow and custom nodes have been imported.</value>
</data>
</root>

3
StabilityMatrix.Avalonia/Models/ImageSource.cs

@ -58,6 +58,9 @@ public record ImageSource : IDisposable, ITemplateKey<ImageSourceTemplateType>
Bitmap = bitmap;
}
[JsonIgnore]
public Uri? Uri => LocalFile?.FullPath != null ? new Uri(LocalFile.FullPath) : RemoteUrl;
/// <inheritdoc />
public ImageSourceTemplateType TemplateKey { get; private set; }

10
StabilityMatrix.Avalonia/Models/OpenArtCustomNode.cs

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace StabilityMatrix.Avalonia.Models;
public class OpenArtCustomNode
{
public required string Title { get; set; }
public List<string> Children { get; set; } = [];
public bool IsInstalled { get; set; }
}

22
StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Avalonia.Platform.Storage;
using StabilityMatrix.Core.Models.Api.OpenArt;
namespace StabilityMatrix.Avalonia.Models;
public class OpenArtMetadata
{
[JsonPropertyName("sm_workflow_data")]
public OpenArtSearchResult? Workflow { get; set; }
[JsonIgnore]
public string? FirstThumbnail => Workflow?.Thumbnails?.Select(x => x.Url).FirstOrDefault()?.ToString();
[JsonIgnore]
public List<IStorageFile>? FilePath { get; set; }
[JsonIgnore]
public bool HasMetadata => Workflow?.Creator != null;
}

21
StabilityMatrix.Avalonia/Models/PackageSteps/UnpackSiteCustomizeStep.cs

@ -0,0 +1,21 @@
using System;
using System.Threading.Tasks;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Python;
namespace StabilityMatrix.Avalonia.Models.PackageSteps;
public class UnpackSiteCustomizeStep(DirectoryPath venvPath) : IPackageStep
{
public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)
{
var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath);
var file = sitePackages.JoinFile("sitecustomize.py");
file.Directory?.Create();
await Assets.PyScriptSiteCustomize.ExtractTo(file);
}
public string ProgressTitle => "Unpacking prerequisites...";
}

2
StabilityMatrix.Avalonia/Services/INavigationService.cs

@ -37,4 +37,6 @@ public interface INavigationService<[SuppressMessage("ReSharper", "UnusedTypePar
void NavigateTo(ViewModelBase viewModel, NavigationTransitionInfo? transitionInfo = null);
bool GoBack();
bool CanGoBack { get; }
}

2
StabilityMatrix.Avalonia/Services/NavigationService.cs

@ -188,4 +188,6 @@ public class NavigationService<T> : INavigationService<T>
_frame.GoBack();
return true;
}
public bool CanGoBack => _frame?.CanGoBack ?? false;
}

40
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -39,16 +39,17 @@
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
<PackageReference Include="AutoComplete.Net" Version="1.2211.2014.42"/>
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.9" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.2" />
<PackageReference Include="Avalonia" Version="11.0.9" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.9" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.9" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.10" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.3" />
<PackageReference Include="Avalonia" Version="11.0.10" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.10" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.10" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.9" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10" />
<PackageReference Include="Avalonia.HtmlRenderer" Version="11.0.0" />
<PackageReference Include="Avalonia.Labs.Controls" Version="11.0.3" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.6" />
<PackageReference Include="Avalonia.Labs.Controls" Version="11.0.10.1" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.10.1" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageReference Include="bodong.Avalonia.PropertyGrid" Version="11.0.6.3" />
<PackageReference Include="bodong.PropertyModels" Version="11.0.6.2" />
@ -57,10 +58,10 @@
<PackageReference Include="DesktopNotifications" Version="1.3.1" />
<PackageReference Include="DesktopNotifications.Avalonia" Version="1.3.1" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="Dock.Avalonia" Version="11.0.0.5" />
<PackageReference Include="Dock.Model.Avalonia" Version="11.0.0.5" />
<PackageReference Include="Dock.Serializer" Version="11.0.0.5" />
<PackageReference Include="DynamicData" Version="8.3.27" />
<PackageReference Include="Dock.Avalonia" Version="11.0.0.7" />
<PackageReference Include="Dock.Model.Avalonia" Version="11.0.0.7" />
<PackageReference Include="Dock.Serializer" Version="11.0.0.7" />
<PackageReference Include="DynamicData" Version="8.4.1" />
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
<PackageReference Include="Exceptionless.DateTimeExtensions" Version="3.4.3" />
<PackageReference Include="FluentAvalonia.BreadcrumbBar" Version="2.0.2" />
@ -77,25 +78,25 @@
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="OneOf" Version="3.0.263" />
<PackageReference Include="Polly" Version="8.3.0" />
<PackageReference Include="Polly" Version="8.3.1" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageReference Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.1.1" />
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.1.2" />
<PackageReference Include="RockLib.Reflection.Optimized" Version="3.0.0" />
<PackageReference Include="Sentry" Version="4.1.2" />
<PackageReference Include="Sentry.NLog" Version="4.1.2" />
<PackageReference Include="Sentry" Version="4.2.1" />
<PackageReference Include="Sentry.NLog" Version="4.2.1" />
<PackageReference Include="SpacedGrid-Avalonia" Version="11.0.0" />
<PackageReference Include="Sylvan.Common" Version="0.4.3" />
<PackageReference Include="Sylvan.Data" Version="0.2.13" />
<PackageReference Include="Sylvan.Data.Csv" Version="1.3.7" />
<PackageReference Include="System.Drawing.Common" Version="8.0.2" />
<PackageReference Include="System.Drawing.Common" Version="8.0.3" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" />
<PackageReference Include="URISchemeTools" Version="1.0.2" />
@ -197,6 +198,9 @@
<DependentUpon>NewInstallerDialog.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Models\OpenArtMetadata.cs">
<Generator>MSBuild:GenerateCodeFromAttributes</Generator>
</Compile>
</ItemGroup>
<!-- set HUSKY to 0 to disable, or opt-in during CI by setting HUSKY to 1 -->

29
StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml

@ -6,7 +6,8 @@
xmlns:mocks="using:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls">
<Design.PreviewWith>
<Panel Width="450" Height="600">
@ -42,15 +43,14 @@
ColumnSpacing="6"
RowSpacing="0">
<!-- Image -->
<controls:BetterAdvancedImage
<labs:AsyncImage
Width="64"
Height="96"
CornerRadius="6"
IsVisible="{Binding Local.PreviewImageFullPathGlobal, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=''}"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding Local.PreviewImageFullPathGlobal, FallbackValue=''}"
Stretch="UniformToFill"
StretchDirection="Both" />
Stretch="UniformToFill"/>
<StackPanel
Grid.Column="1"
MaxWidth="300"
@ -125,18 +125,14 @@
ColumnSpacing="8"
TextBlock.TextTrimming="CharacterEllipsis"
TextBlock.TextWrapping="NoWrap">
<controls:BetterAdvancedImage
<labs:AsyncImage
Grid.RowSpan="2"
Width="42"
Height="42"
RenderOptions.BitmapInterpolationMode="HighQuality"
CornerRadius="40"
Source="{Binding Local.PreviewImageFullPathGlobal}"
Stretch="UniformToFill"
StretchDirection="Both">
<controls:BetterAdvancedImage.Clip>
<EllipseGeometry Rect="0,0,42,42" />
</controls:BetterAdvancedImage.Clip>
</controls:BetterAdvancedImage>
Stretch="UniformToFill"/>
<!-- Text -->
<sg:SpacedGrid
@ -217,18 +213,15 @@
ColumnSpacing="8"
TextBlock.TextTrimming="CharacterEllipsis"
TextBlock.TextWrapping="NoWrap">
<controls:BetterAdvancedImage
<labs:AsyncImage
Grid.RowSpan="2"
Width="36"
Height="36"
CornerRadius="60"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding Local.PreviewImageFullPathGlobal}"
Stretch="UniformToFill"
StretchDirection="Both">
<controls:BetterAdvancedImage.Clip>
<EllipseGeometry Rect="0,0,36,36" />
</controls:BetterAdvancedImage.Clip>
</controls:BetterAdvancedImage>
Stretch="UniformToFill">
</labs:AsyncImage>
<!-- Text -->
<sg:SpacedGrid

25
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
@ -19,6 +20,7 @@ using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Database;
@ -158,7 +160,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
// Try to find a valid image
var image = images
?.Where(img => LocalModelFile.SupportedImageExtensions.Any(img.Url.Contains))
.FirstOrDefault(image => nsfwEnabled || image.Nsfw == "None");
.FirstOrDefault(image => nsfwEnabled || image.NsfwLevel <= 1);
if (image != null)
{
CardImage = new Uri(image.Url);
@ -216,7 +218,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
MaxDialogHeight = 950,
};
var prunedDescription = PruneDescription(model);
var prunedDescription = Utilities.RemoveHtml(model.Description);
var viewModel = dialogFactory.Get<SelectModelVersionViewModel>();
viewModel.Dialog = dialog;
@ -255,7 +257,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
{
var subFolder =
viewModel?.SelectedInstallLocation
?? Path.Combine("Models", model.Type.ConvertTo<SharedFolderType>().GetStringValue());
?? Path.Combine(@"Models", model.Type.ConvertTo<SharedFolderType>().GetStringValue());
downloadPath = Path.Combine(settingsManager.LibraryDir, subFolder);
}
@ -263,23 +265,6 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel
await DoImport(model, downloadPath, selectedVersion, selectedFile);
}
private static string PruneDescription(CivitModel model)
{
var prunedDescription =
model
.Description?.Replace("<br/>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("<br />", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</p>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h1>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h2>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h3>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h4>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h5>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h6>", $"{Environment.NewLine}{Environment.NewLine}") ?? string.Empty;
prunedDescription = HtmlRegex().Replace(prunedDescription, string.Empty);
return prunedDescription;
}
private static async Task<FilePath> SaveCmInfo(
CivitModel model,
CivitModelVersion modelVersion,

21
StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs

@ -12,6 +12,7 @@ using DynamicData;
using DynamicData.Binding;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Models.PackageSteps;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@ -107,18 +108,19 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
{
Task.Run(async () =>
{
var installLocation = Path.Combine(
settingsManager.LibraryDir,
"Packages",
selectedPackage.Name
);
var steps = new List<IPackageStep>
{
new SetPackageInstallingStep(settingsManager, selectedPackage.Name),
new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage)
new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage),
};
// get latest version & download & install
var installLocation = Path.Combine(
settingsManager.LibraryDir,
"Packages",
selectedPackage.Name
);
if (Directory.Exists(installLocation))
{
var installPath = new DirectoryPath(installLocation);
@ -148,6 +150,11 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
);
steps.Add(downloadStep);
var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(
Path.Combine(installLocation, "venv")
);
steps.Add(unpackSiteCustomizeStep);
var installStep = new InstallPackageStep(
selectedPackage,
torchVersion,
@ -186,7 +193,7 @@ public partial class NewOneClickInstallViewModel : ContentDialogViewModelBase
{
ShowDialogOnStart = false,
HideCloseButton = false,
ModificationCompleteMessage = $"{installedPackage.DisplayName} Install Complete",
ModificationCompleteMessage = $"{selectedPackage.DisplayName} installed successfully"
};
runner

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

@ -196,7 +196,7 @@ public partial class OneClickInstallViewModel : ContentDialogViewModelBase
{
ShowDialogOnStart = true,
HideCloseButton = true,
ModificationCompleteMessage = "Install complete"
ModificationCompleteMessage = $"{SelectedPackage.DisplayName} installed successfully"
};
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);

185
StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs

@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Models.Packages.Extensions;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(OpenArtWorkflowDialog))]
[ManagedService]
[Transient]
public partial class OpenArtWorkflowViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory
) : ContentDialogViewModelBase
{
public required OpenArtSearchResult Workflow { get; init; }
[ObservableProperty]
private ObservableCollection<OpenArtCustomNode> customNodes = [];
[ObservableProperty]
private string prunedDescription = string.Empty;
[ObservableProperty]
private bool installRequiredNodes = true;
[ObservableProperty]
private InstalledPackage? selectedPackage;
public PackagePair? SelectedPackagePair =>
SelectedPackage is { } package ? packageFactory.GetPackagePair(package) : null;
public List<InstalledPackage> AvailablePackages =>
settingsManager
.Settings.InstalledPackages.Where(package => package.PackageName == "ComfyUI")
.ToList();
public List<PackageExtension> MissingNodes { get; } = [];
public override async Task OnLoadedAsync()
{
if (Design.IsDesignMode)
return;
if (settingsManager.Settings.PreferredWorkflowPackage is { } preferredPackage)
{
SelectedPackage = preferredPackage;
}
else
{
SelectedPackage = AvailablePackages.FirstOrDefault();
}
if (SelectedPackage == null)
{
InstallRequiredNodes = false;
}
CustomNodes = new ObservableCollection<OpenArtCustomNode>(
await ParseNodes(Workflow.NodesIndex.ToList())
);
PrunedDescription = Utilities.RemoveHtml(Workflow.Description);
}
partial void OnSelectedPackageChanged(InstalledPackage? oldValue, InstalledPackage? newValue)
{
if (oldValue is null)
return;
settingsManager.Transaction(settings =>
{
settings.PreferredWorkflowPackage = newValue;
});
OnLoadedAsync().SafeFireAndForget();
}
[Localizable(false)]
private async Task<List<OpenArtCustomNode>> ParseNodes(List<string> nodes)
{
var indexOfFirstDot = nodes.IndexOf(".");
if (indexOfFirstDot != -1)
{
nodes = nodes[(indexOfFirstDot + 1)..];
}
var installedNodesNames = new HashSet<string>();
var nameToManifestNodes = new Dictionary<string, PackageExtension>();
var packagePair = SelectedPackagePair;
if (packagePair?.BasePackage.ExtensionManager is { } extensionManager)
{
var installedNodes = (
await extensionManager.GetInstalledExtensionsLiteAsync(packagePair.InstalledPackage)
).ToList();
var manifestExtensionsMap = await extensionManager.GetManifestExtensionsMapAsync(
extensionManager.GetManifests(packagePair.InstalledPackage)
);
// Add manifestExtensions definition to installedNodes if matching git repository url
installedNodes = installedNodes
.Select(installedNode =>
{
if (
installedNode.GitRepositoryUrl is not null
&& manifestExtensionsMap.TryGetValue(
installedNode.GitRepositoryUrl,
out var manifestExtension
)
)
{
installedNode = installedNode with { Definition = manifestExtension };
}
return installedNode;
})
.ToList();
// There may be duplicate titles, deduplicate by using the first one
nameToManifestNodes = manifestExtensionsMap
.GroupBy(x => x.Value.Title)
.ToDictionary(x => x.Key, x => x.First().Value);
installedNodesNames = installedNodes.Select(x => x.Title).ToHashSet();
}
var sections = new List<OpenArtCustomNode>();
OpenArtCustomNode? currentSection = null;
foreach (var node in nodes)
{
if (node is "." or ",")
{
currentSection = null; // End of the current section
continue;
}
if (currentSection == null)
{
currentSection = new OpenArtCustomNode
{
Title = node,
IsInstalled = installedNodesNames.Contains(node)
};
// Add missing nodes to the list
if (
!currentSection.IsInstalled && nameToManifestNodes.TryGetValue(node, out var manifestNode)
)
{
MissingNodes.Add(manifestNode);
}
sections.Add(currentSection);
}
else
{
currentSection.Children.Add(node);
}
}
if (sections.FirstOrDefault(x => x.Title == "ComfyUI") != null)
{
sections = sections.Where(x => x.Title != "ComfyUI").ToList();
}
return sections;
}
}

2
StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs

@ -75,7 +75,7 @@ public partial class RecommendedModelsViewModel : ContentDialogViewModelBase
CivitModels
.Connect()
.DeferUntilLoaded()
.Filter(f => f.ModelVersion.BaseModel == "SDXL 1.0")
.Filter(f => f.ModelVersion.BaseModel == "SDXL 1.0" || f.ModelVersion.BaseModel == "Pony")
.Bind(SdxlModels)
.Subscribe();
}

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

@ -99,7 +99,7 @@ public partial class SelectModelVersionViewModel(
var allImages = value
?.ModelVersion
?.Images
?.Where(img => nsfwEnabled || img.Nsfw == "None")
?.Where(img => nsfwEnabled || img.NsfwLevel <= 1)
?.Select(x => new ImageSource(x.Url))
.ToList();
@ -316,7 +316,9 @@ public partial class SelectModelVersionViewModel(
installLocations.Add(downloadDirectory.ToString().Replace(rootModelsDirectory, "Models"));
foreach (var directory in downloadDirectory.EnumerateDirectories())
foreach (
var directory in downloadDirectory.EnumerateDirectories(searchOption: SearchOption.AllDirectories)
)
{
installLocations.Add(directory.ToString().Replace(rootModelsDirectory, "Models"));
}

173
StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs

@ -0,0 +1,173 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(InstalledWorkflowsPage))]
[Singleton]
public partial class InstalledWorkflowsViewModel(
ISettingsManager settingsManager,
INotificationService notificationService
) : TabViewModelBase, IDisposable
{
public override string Header => Resources.TabLabel_InstalledWorkflows;
private readonly SourceCache<OpenArtMetadata, string> workflowsCache =
new(x => x.Workflow?.Id ?? Guid.NewGuid().ToString());
[ObservableProperty]
private IObservableCollection<OpenArtMetadata> displayedWorkflows =
new ObservableCollectionExtended<OpenArtMetadata>();
protected override async Task OnInitialLoadedAsync()
{
await base.OnInitialLoadedAsync();
workflowsCache.Connect().DeferUntilLoaded().Bind(DisplayedWorkflows).Subscribe();
if (Design.IsDesignMode)
return;
await LoadInstalledWorkflowsAsync();
EventManager.Instance.WorkflowInstalled += OnWorkflowInstalled;
}
[RelayCommand]
private async Task LoadInstalledWorkflowsAsync()
{
workflowsCache.Clear();
if (!Directory.Exists(settingsManager.WorkflowDirectory))
{
Directory.CreateDirectory(settingsManager.WorkflowDirectory);
}
foreach (
var workflowPath in Directory.EnumerateFiles(
settingsManager.WorkflowDirectory,
"*.json",
SearchOption.AllDirectories
)
)
{
try
{
var json = await File.ReadAllTextAsync(workflowPath);
var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json);
if (metadata?.Workflow == null)
{
metadata = new OpenArtMetadata
{
Workflow = new OpenArtSearchResult
{
Id = Guid.NewGuid().ToString(),
Name = Path.GetFileNameWithoutExtension(workflowPath)
}
};
}
metadata.FilePath = [await App.StorageProvider.TryGetFileFromPathAsync(workflowPath)];
workflowsCache.AddOrUpdate(metadata);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
[RelayCommand]
private async Task OpenInExplorer(OpenArtMetadata metadata)
{
if (metadata.FilePath == null)
return;
var path = metadata.FilePath.FirstOrDefault()?.Path.ToString();
if (string.IsNullOrWhiteSpace(path))
return;
await ProcessRunner.OpenFileBrowser(path);
}
[RelayCommand]
private void OpenOnOpenArt(OpenArtMetadata metadata)
{
if (metadata.Workflow == null)
return;
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{metadata.Workflow.Id}");
}
[RelayCommand]
private async Task DeleteAsync(OpenArtMetadata metadata)
{
var confirmationDialog = new BetterContentDialog
{
Title = Resources.Label_AreYouSure,
Content = Resources.Label_ActionCannotBeUndone,
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
IsSecondaryButtonEnabled = true,
};
var dialogResult = await confirmationDialog.ShowAsync();
if (dialogResult != ContentDialogResult.Primary)
return;
await using var delay = new MinimumDelay(200, 500);
var path = metadata?.FilePath?.FirstOrDefault()?.Path.ToString().Replace("file:///", "");
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
{
await notificationService.TryAsync(
Task.Run(() => File.Delete(path)),
message: "Error deleting workflow"
);
var id = metadata?.Workflow?.Id;
if (!string.IsNullOrWhiteSpace(id))
{
workflowsCache.Remove(id);
}
}
notificationService.Show(
Resources.Label_WorkflowDeleted,
string.Format(Resources.Label_WorkflowDeletedSuccessfully, metadata?.Workflow?.Name)
);
}
private void OnWorkflowInstalled(object? sender, EventArgs e)
{
LoadInstalledWorkflowsAsync().SafeFireAndForget();
}
public void Dispose()
{
workflowsCache.Dispose();
EventManager.Instance.WorkflowInstalled -= OnWorkflowInstalled;
}
}

348
StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs

@ -0,0 +1,348 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Refit;
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.Dialogs;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Api;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using Resources = StabilityMatrix.Avalonia.Languages.Resources;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(OpenArtBrowserPage))]
[Singleton]
public partial class OpenArtBrowserViewModel(
IOpenArtApi openArtApi,
INotificationService notificationService,
ISettingsManager settingsManager,
IPackageFactory packageFactory,
ServiceManager<ViewModelBase> vmFactory
) : TabViewModelBase, IInfinitelyScroll
{
private const int PageSize = 20;
public override string Header => Resources.Label_OpenArtBrowser;
private readonly SourceCache<OpenArtSearchResult, string> searchResultsCache = new(x => x.Id);
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(PageCount), nameof(CanGoBack), nameof(CanGoForward), nameof(CanGoToEnd))]
private OpenArtSearchResponse? latestSearchResponse;
[ObservableProperty]
private IObservableCollection<OpenArtSearchResult> searchResults =
new ObservableCollectionExtended<OpenArtSearchResult>();
[ObservableProperty]
private string searchQuery = string.Empty;
[ObservableProperty]
private bool isLoading;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(InternalPageNumber), nameof(CanGoBack))]
private int displayedPageNumber = 1;
public int InternalPageNumber => DisplayedPageNumber - 1;
public int PageCount =>
Math.Max(
1,
Convert.ToInt32(Math.Ceiling((LatestSearchResponse?.Total ?? 0) / Convert.ToDouble(PageSize)))
);
public bool CanGoBack =>
string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && InternalPageNumber > 0;
public bool CanGoForward =>
!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) || PageCount > InternalPageNumber + 1;
public bool CanGoToEnd =>
string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && PageCount > InternalPageNumber + 1;
public IEnumerable<string> AllSortModes => ["Trending", "Latest", "Most Downloaded", "Most Liked"];
[ObservableProperty]
private string? selectedSortMode;
protected override void OnInitialLoaded()
{
searchResultsCache.Connect().DeferUntilLoaded().Bind(SearchResults).Subscribe();
SelectedSortMode = AllSortModes.First();
}
[RelayCommand]
private async Task FirstPage()
{
DisplayedPageNumber = 1;
searchResultsCache.Clear();
await DoSearch();
}
[RelayCommand]
private async Task PreviousPage()
{
DisplayedPageNumber--;
searchResultsCache.Clear();
await DoSearch(InternalPageNumber);
}
[RelayCommand]
private async Task NextPage()
{
if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
{
DisplayedPageNumber++;
}
searchResultsCache.Clear();
await DoSearch(InternalPageNumber);
}
[RelayCommand]
private async Task LastPage()
{
if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
{
DisplayedPageNumber = PageCount;
}
searchResultsCache.Clear();
await DoSearch(PageCount - 1);
}
[Localizable(false)]
[RelayCommand]
private void OpenModel(OpenArtSearchResult workflow)
{
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{workflow.Id}");
}
[RelayCommand]
private async Task SearchButton()
{
DisplayedPageNumber = 1;
LatestSearchResponse = null;
searchResultsCache.Clear();
await DoSearch();
}
[RelayCommand]
private async Task OpenWorkflow(OpenArtSearchResult workflow)
{
var vm = new OpenArtWorkflowViewModel(settingsManager, packageFactory) { Workflow = workflow };
var dialog = new BetterContentDialog
{
IsPrimaryButtonEnabled = true,
IsSecondaryButtonEnabled = true,
PrimaryButtonText = Resources.Action_Import,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
IsFooterVisible = true,
MaxDialogWidth = 750,
MaxDialogHeight = 850,
CloseOnClickOutside = true,
Content = vm
};
var result = await dialog.ShowAsync();
if (result != ContentDialogResult.Primary)
return;
List<IPackageStep> steps =
[
new DownloadOpenArtWorkflowStep(openArtApi, vm.Workflow, settingsManager)
];
// Add install steps if missing nodes and preferred
if (
vm is
{
InstallRequiredNodes: true,
MissingNodes: { Count: > 0 } missingNodes,
SelectedPackage: not null,
SelectedPackagePair: not null
}
)
{
var extensionManager = vm.SelectedPackagePair.BasePackage.ExtensionManager!;
steps.AddRange(
missingNodes.Select(
extension =>
new InstallExtensionStep(
extensionManager,
vm.SelectedPackagePair.InstalledPackage,
extension
)
)
);
}
var runner = new PackageModificationRunner
{
ShowDialogOnStart = true,
ModificationCompleteTitle = Resources.Label_WorkflowImported,
ModificationCompleteMessage = Resources.Label_FinishedImportingWorkflow
};
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);
notificationService.Show(
Resources.Label_WorkflowImported,
Resources.Label_WorkflowImportComplete,
NotificationType.Success
);
EventManager.Instance.OnWorkflowInstalled();
}
[RelayCommand]
private void OpenOnOpenArt(OpenArtSearchResult? workflow)
{
if (workflow?.Id == null)
return;
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{workflow.Id}");
}
private async Task DoSearch(int page = 0)
{
IsLoading = true;
try
{
OpenArtSearchResponse? response = null;
if (string.IsNullOrWhiteSpace(SearchQuery))
{
var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) };
if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
{
request.Cursor = LatestSearchResponse.NextCursor;
}
response = await openArtApi.GetFeedAsync(request);
}
else
{
response = await openArtApi.SearchAsync(
new OpenArtSearchRequest
{
Keyword = SearchQuery,
PageSize = PageSize,
CurrentPage = page
}
);
}
foreach (var item in response.Items)
{
searchResultsCache.AddOrUpdate(item);
}
LatestSearchResponse = response;
}
catch (ApiException e)
{
notificationService.Show(Resources.Label_ErrorRetrievingWorkflows, e.Message);
}
finally
{
IsLoading = false;
}
}
partial void OnSelectedSortModeChanged(string? value)
{
if (value is null || SearchResults.Count == 0)
return;
searchResultsCache.Clear();
LatestSearchResponse = null;
DoSearch().SafeFireAndForget();
}
public async Task LoadNextPageAsync()
{
if (!CanGoForward)
return;
try
{
OpenArtSearchResponse? response = null;
if (string.IsNullOrWhiteSpace(SearchQuery))
{
var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) };
if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))
{
request.Cursor = LatestSearchResponse.NextCursor;
}
response = await openArtApi.GetFeedAsync(request);
}
else
{
DisplayedPageNumber++;
response = await openArtApi.SearchAsync(
new OpenArtSearchRequest
{
Keyword = SearchQuery,
PageSize = PageSize,
CurrentPage = InternalPageNumber
}
);
}
foreach (var item in response.Items)
{
searchResultsCache.AddOrUpdate(item);
}
LatestSearchResponse = response;
}
catch (ApiException e)
{
notificationService.Show("Unable to load the next page", e.Message);
}
}
private static string GetSortMode(string? sortMode)
{
return sortMode switch
{
"Trending" => "trending",
"Latest" => "latest",
"Most Downloaded" => "most_downloaded",
"Most Liked" => "most_liked",
_ => "trending"
};
}
}

12
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -395,12 +395,13 @@ public partial class OutputsPageViewModel : PageViewModelBase
var selected = Outputs.Where(o => o.IsSelected).ToList();
Debug.Assert(selected.Count == NumItemsSelected);
var imagesToRemove = new List<LocalImageFile>();
foreach (var output in selected)
{
// Delete the file
var imageFile = new FilePath(output.ImageFile.AbsolutePath);
var result = await notificationService.TryAsync(imageFile.DeleteAsync());
if (!result.IsSuccessful)
{
continue;
@ -413,15 +414,10 @@ public partial class OutputsPageViewModel : PageViewModelBase
await notificationService.TryAsync(sideCar.DeleteAsync());
}
OutputsCache.Remove(output.ImageFile);
// Invalidate cache
if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
{
loader.RemoveAllNamesFromCache(imageFile.Name);
}
imagesToRemove.Add(output.ImageFile);
}
OutputsCache.Remove(imagesToRemove);
NumItemsSelected = 0;
ClearSelection();
}

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

@ -54,7 +54,7 @@ public partial class PackageCardViewModel(
private InstalledPackage? package;
[ObservableProperty]
private string? cardImageSource;
private Uri? cardImageSource;
[ObservableProperty]
private bool isUpdateAvailable;
@ -127,7 +127,7 @@ public partial class PackageCardViewModel(
)
{
IsUnknownPackage = true;
CardImageSource = "";
CardImageSource = null;
InstalledVersion = "Unknown";
}
else
@ -135,7 +135,7 @@ public partial class PackageCardViewModel(
IsUnknownPackage = false;
var basePackage = packageFactory[value.PackageName];
CardImageSource = basePackage?.PreviewImageUri.ToString() ?? Assets.NoImage.ToString();
CardImageSource = basePackage?.PreviewImageUri ?? Assets.NoImage;
InstalledVersion = value.Version?.DisplayVersion ?? "Unknown";
CanUseConfigMethod =
basePackage?.AvailableSharedFolderMethods.Contains(SharedFolderMethod.Configuration) ?? false;

18
StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs

@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models.PackageSteps;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
@ -85,6 +86,9 @@ public partial class PackageInstallDetailViewModel(
[ObservableProperty]
private GitCommit? selectedCommit;
[ObservableProperty]
private bool isOutputSharingEnabled = true;
[ObservableProperty]
private bool canInstall;
@ -97,6 +101,8 @@ public partial class PackageInstallDetailViewModel(
OnInstallNameChanged(InstallName);
CanInstall = false;
SelectedTorchVersion = SelectedPackage.GetRecommendedTorchVersion();
SelectedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod;
@ -183,6 +189,7 @@ public partial class PackageInstallDetailViewModel(
}
var prereqStep = new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, SelectedPackage);
var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(Path.Combine(installLocation, "venv"));
var downloadOptions = new DownloadPackageVersionOptions();
var installedVersion = new InstalledPackageVersion();
@ -224,6 +231,8 @@ public partial class PackageInstallDetailViewModel(
installLocation
);
var setupOutputSharingStep = new SetupOutputSharingStep(SelectedPackage, installLocation);
var package = new InstalledPackage
{
DisplayName = InstallName,
@ -234,7 +243,8 @@ public partial class PackageInstallDetailViewModel(
LaunchCommand = SelectedPackage.LaunchCommand,
LastUpdateCheck = DateTimeOffset.Now,
PreferredTorchVersion = SelectedTorchVersion,
PreferredSharedFolderMethod = SelectedSharedFolderMethod
PreferredSharedFolderMethod = SelectedSharedFolderMethod,
UseSharedOutputFolder = IsOutputSharingEnabled
};
var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, package);
@ -244,11 +254,17 @@ public partial class PackageInstallDetailViewModel(
setPackageInstallingStep,
prereqStep,
downloadStep,
unpackSiteCustomizeStep,
installStep,
setupModelFoldersStep,
addInstalledPackageStep
};
if (IsOutputSharingEnabled)
{
steps.Insert(steps.IndexOf(addInstalledPackageStep), setupOutputSharingStep);
}
var packageName = SelectedPackage.Name;
var runner = new PackageModificationRunner

13
StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs

@ -49,10 +49,21 @@ public class PackageInstallProgressItemViewModel : ProgressItemViewModelBase
Name = packageModificationRunner.CurrentStep?.ProgressTitle;
Failed = packageModificationRunner.Failed;
if (string.IsNullOrWhiteSpace(e.Message) || e.Message.Contains("Downloading..."))
if (e.ProcessOutput == null && string.IsNullOrWhiteSpace(e.Message))
return;
if (!string.IsNullOrWhiteSpace(e.Message) && e.Message.Contains("Downloading..."))
return;
if (e.ProcessOutput != null)
{
Progress.Console.Post(e.ProcessOutput.Value);
}
else
{
Progress.Console.PostLine(e.Message);
}
EventManager.Instance.OnScrollToBottomRequested();
if (

10
StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs

@ -104,6 +104,14 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I
}
}
[RelayCommand]
private async Task Restart()
{
await Stop();
await Task.Delay(100);
LaunchPackage();
}
[RelayCommand]
private void LaunchPackage()
{
@ -113,10 +121,10 @@ public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, I
[RelayCommand]
private async Task Stop()
{
IsRunning = false;
await runningPackageService.StopPackage(RunningPackage.InstalledPackage.Id);
Console.PostLine($"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}");
await Console.StopUpdatesAsync();
IsRunning = false;
}
[RelayCommand]

10
StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs

@ -130,6 +130,9 @@ public partial class MainSettingsViewModel : PageViewModelBase
[ObservableProperty]
private HolidayMode holidayModeSetting;
[ObservableProperty]
private bool infinitelyScrollWorkflowBrowser;
#region System Info
private static Lazy<IReadOnlyList<GpuInfo>> GpuInfosLazy { get; } =
@ -218,6 +221,13 @@ public partial class MainSettingsViewModel : PageViewModelBase
settings => settings.HolidayModeSetting
);
settingsManager.RelayPropertyFor(
this,
vm => vm.InfinitelyScrollWorkflowBrowser,
settings => settings.IsWorkflowInfiniteScrollEnabled,
true
);
DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);
hardwareInfoUpdateTimer.Tick += OnHardwareInfoUpdateTimerTick;

39
StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(WorkflowsPage))]
[Singleton]
public partial class WorkflowsPageViewModel : PageViewModelBase
{
public override string Title => Resources.Label_Workflows;
public override IconSource IconSource => new FASymbolIconSource { Symbol = "fa-solid fa-circle-nodes" };
public IReadOnlyList<TabItem> Pages { get; }
[ObservableProperty]
private TabItem? selectedPage;
/// <inheritdoc/>
public WorkflowsPageViewModel(
OpenArtBrowserViewModel openArtBrowserViewModel,
InstalledWorkflowsViewModel installedWorkflowsViewModel
)
{
Pages = new List<TabItem>(
new List<TabViewModelBase>([openArtBrowserViewModel, installedWorkflowsViewModel]).Select(
vm => new TabItem { Header = vm.Header, Content = vm }
)
);
SelectedPage = Pages.FirstOrDefault();
}
}

6
StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml

@ -13,6 +13,7 @@
xmlns:api="clr-namespace:StabilityMatrix.Core.Models.Api;assembly=StabilityMatrix.Core"
xmlns:generic="clr-namespace:System.Collections.Generic;assembly=System.Collections"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
d:DataContext="{x:Static mocks:DesignData.CheckpointsPageViewModel}"
x:CompileBindings="True"
x:DataType="vm:CheckpointsPageViewModel"
@ -138,7 +139,7 @@
TextWrapping="WrapWithOverflow"
IsVisible="{Binding IsConnectedModel}" />
<!-- Image -->
<controls:BetterAdvancedImage
<labs:AsyncImage
Margin="0,0,0,4"
CornerRadius="4"
Source="{Binding PreviewImagePath}"
@ -175,12 +176,13 @@
IsVisible="{Binding IsConnectedModel}" />
</StackPanel>
<controls:BetterAdvancedImage Grid.Row="1"
<labs:AsyncImage Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0, 4,0,4"
CornerRadius="8"
Source="{Binding PreviewImagePath}"
ClipToBounds="True"
Stretch="UniformToFill"
Height="300"
Width="300"

19
StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml

@ -15,7 +15,7 @@
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
xmlns:controls1="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
d:DataContext="{x:Static designData:DesignData.CivitAiBrowserViewModel}"
d:DesignHeight="700"
@ -32,7 +32,7 @@
</Transitions>
</Setter>
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Style Selector="^ labs|AsyncImage">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform"
@ -48,7 +48,7 @@
<Style Selector="^:pointerover">
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000"/>
<Setter Property="Cursor" Value="Hand" />
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Style Selector="^ labs|AsyncImage">
<Setter Property="CornerRadius" Value="12"/>
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)"/>
</Style>
@ -60,7 +60,7 @@
<Style Selector="^:not(:pointerover)">
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000"/>
<Setter Property="Cursor" Value="Arrow" />
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Style Selector="^ labs|AsyncImage">
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="RenderTransform" Value="scale(1, 1)"/>
</Style>
@ -103,7 +103,7 @@
CommandParameter="{Binding CivitModel}"
IsEnabled="{Binding !IsImporting}">
<Grid RowDefinitions="*, Auto">
<controls1:AsyncImage Grid.Row="0"
<labs:AsyncImage Grid.Row="0"
CornerRadius="8"
VerticalAlignment="Top"
HorizontalAlignment="Center"
@ -115,8 +115,8 @@
<!-- <controls:BetterAdvancedImage.RenderTransform> -->
<!-- <RotateTransform Angle="315"></RotateTransform> -->
<!-- </controls:BetterAdvancedImage.RenderTransform> -->
</controls1:AsyncImage>
<controls1:AsyncImage
</labs:AsyncImage>
<labs:AsyncImage
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
@ -182,14 +182,15 @@
Classes="transparent"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<controls1:AsyncImage
<labs:AsyncImage
Width="22"
Height="22"
Effect="{StaticResource ImageDropShadowEffect}"
CornerRadius="11"
RenderOptions.BitmapInterpolationMode="HighQuality"
IsVisible="{Binding CivitModel.Creator.Image, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Source="{Binding CivitModel.Creator.Image}"/>
Source="{Binding CivitModel.Creator.Image}">
</labs:AsyncImage>
<TextBlock
VerticalAlignment="Center"
Effect="{StaticResource TextDropShadowEffect}"

10
StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml

@ -34,8 +34,16 @@
Command="{Binding LaunchPackageCommand}"
VerticalAlignment="Center"
Label="{x:Static lang:Resources.Action_Launch}" />
<ui:CommandBarButton
IconSource="Refresh"
Margin="4,0,0,0"
Classes="info"
IsVisible="{Binding IsRunning}"
Command="{Binding RestartCommand}"
VerticalAlignment="Center"
Label="{x:Static lang:Resources.Action_Restart}" />
<ui:CommandBarSeparator Margin="6,0,4,0"/>
<ui:CommandBarSeparator Margin="16,0,16,0"/>
<ui:CommandBarToggleButton
VerticalAlignment="Center"

23
StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml

@ -128,7 +128,7 @@
PlacementMargin="16,0,16,0"
TailVisibility="Collapsed"
Target="{Binding #InfoButton}">
<StackPanel Spacing="4" Margin="4,0" DataContext="{Binding LocalImageFile.GenerationParameters}">
<StackPanel Spacing="8" Margin="4,0" DataContext="{Binding LocalImageFile.GenerationParameters}">
<Grid RowDefinitions="Auto,*">
<TextBlock Text="Prompt" Theme="{DynamicResource BodyStrongTextBlockStyle}" />
<SelectableTextBlock
@ -150,6 +150,27 @@
Text="{Binding ModelName}" />
</Grid>
<Grid RowDefinitions="Auto,*">
<TextBlock Text="Sampler" Theme="{DynamicResource BodyStrongTextBlockStyle}" />
<SelectableTextBlock
Grid.Row="1"
Text="{Binding Sampler}" />
</Grid>
<Grid RowDefinitions="Auto,*">
<TextBlock Text="Steps" Theme="{DynamicResource BodyStrongTextBlockStyle}" />
<SelectableTextBlock
Grid.Row="1"
Text="{Binding Steps}" />
</Grid>
<Grid RowDefinitions="Auto,*">
<TextBlock Text="{x:Static lang:Resources.Label_CFGScale}" Theme="{DynamicResource BodyStrongTextBlockStyle}" />
<SelectableTextBlock
Grid.Row="1"
Text="{Binding CfgScale}" />
</Grid>
<Grid RowDefinitions="Auto,*">
<TextBlock Text="Seed" Theme="{DynamicResource BodyStrongTextBlockStyle}" />
<SelectableTextBlock

136
StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml

@ -0,0 +1,136 @@
<controls:UserControlBase
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.OpenArtWorkflowDialog"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
d:DataContext="{x:Static designData:DesignData.OpenArtWorkflowViewModel}"
d:DesignHeight="650"
d:DesignWidth="600"
x:DataType="dialogs:OpenArtWorkflowViewModel"
mc:Ignorable="d">
<Grid
Width="600"
HorizontalAlignment="Stretch"
ColumnDefinitions="*, 2*"
RowDefinitions="Auto, Auto, Auto, Auto, Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="8,8,0,4"
FontSize="20"
TextAlignment="Left"
ToolTip.Tip="{Binding Workflow.Name}">
<Run Text="{Binding Workflow.Name}" />
<Run Text="- by" />
<Run Text="{Binding Workflow.Creator.Name}" />
</TextBlock>
<labs:AsyncImage
Grid.Row="2"
Grid.Column="0"
Height="300"
Margin="8"
CornerRadius="8"
Source="{Binding Workflow.Thumbnails[0].Url}"
Stretch="UniformToFill" />
<controls:Card
Grid.Row="2"
Grid.Column="1"
Margin="8"
VerticalAlignment="Top">
<ScrollViewer MaxHeight="270">
<TextBlock
Margin="4"
Text="{Binding PrunedDescription}"
TextWrapping="Wrap" />
</ScrollViewer>
</controls:Card>
<Expander
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="8,8"
ExpandDirection="Down"
Header="{x:Static lang:Resources.Label_NodeDetails}">
<ScrollViewer MaxHeight="225">
<ItemsControl ItemsSource="{Binding CustomNodes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type models:OpenArtCustomNode}">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock
FontSize="16"
FontWeight="SemiBold"
Text="{Binding Title}" />
<avalonia:Icon
Margin="4"
VerticalAlignment="Center"
Foreground="Lime"
IsVisible="{Binding IsInstalled}"
Value="fa-solid fa-circle-check" />
</StackPanel>
<ItemsControl Margin="0,4" ItemsSource="{Binding Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type system:String}">
<StackPanel Orientation="Vertical">
<TextBlock Margin="4,0,0,0" Text="{Binding ., StringFormat={} - {0}}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Expander>
<Expander
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="8"
Header="Options">
<StackPanel Spacing="4">
<ui:SettingsExpanderItem Content="Install Required Nodes">
<ui:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding InstallRequiredNodes}"
IsEnabled="{Binding AvailablePackages.Count}"/>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="Target Package">
<ui:SettingsExpanderItem.Footer>
<ComboBox
DisplayMemberBinding="{Binding DisplayName}"
ItemsSource="{Binding AvailablePackages}"
SelectedItem="{Binding SelectedPackage}"/>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</StackPanel>
</Expander>
</Grid>
</controls:UserControlBase>

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

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

254
StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml

@ -0,0 +1,254 @@
<controls:UserControlBase xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:openArt="clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
xmlns:fluent="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
d:DataContext="{x:Static designData:DesignData.InstalledWorkflowsViewModel}"
x:DataType="viewModels:InstalledWorkflowsViewModel"
x:Class="StabilityMatrix.Avalonia.Views.InstalledWorkflowsPage">
<UserControl.Styles>
<Style Selector="Border#HoverBorder">
<Setter Property="Transitions">
<Transitions>
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237" />
</Transitions>
</Setter>
<Style Selector="^ labs|AsyncImage">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform"
Duration="0:0:0.237">
<TransformOperationsTransition.Easing>
<QuadraticEaseInOut />
</TransformOperationsTransition.Easing>
</TransformOperationsTransition>
</Transitions>
</Setter>
</Style>
<Style Selector="^:pointerover">
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000" />
<Setter Property="Cursor" Value="Hand" />
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Setter Property="CornerRadius" Value="12" />
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)" />
</Style>
<Style Selector="^ Border#ModelCardBottom">
<Setter Property="Background" Value="#CC000000" />
</Style>
</Style>
<Style Selector="^:not(:pointerover)">
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000" />
<Setter Property="Cursor" Value="Arrow" />
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Setter Property="CornerRadius" Value="8" />
<Setter Property="RenderTransform" Value="scale(1, 1)" />
</Style>
<Style Selector="^ Border#ModelCardBottom">
<Setter Property="Background" Value="#99000000" />
</Style>
</Style>
</Style>
</UserControl.Styles>
<UserControl.Resources>
<input:StandardUICommand
x:Key="OpenInExplorerCommand"
Command="{Binding OpenInExplorerCommand}" />
<input:StandardUICommand
x:Key="OpenOnOpenArtCommand"
Command="{Binding OpenOnOpenArtCommand}" />
<input:StandardUICommand
x:Key="DeleteCommand"
Command="{Binding DeleteCommand}" />
</UserControl.Resources>
<Grid RowDefinitions="Auto, *">
<controls1:CommandBar Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
VerticalContentAlignment="Center"
DefaultLabelPosition="Right">
<controls1:CommandBar.PrimaryCommands>
<controls1:CommandBarButton
IconSource="Refresh"
VerticalAlignment="Center"
Label="{x:Static lang:Resources.Action_Refresh}"
Command="{Binding LoadInstalledWorkflowsCommand}" />
<controls1:CommandBarSeparator />
<controls1:CommandBarElementContainer>
<StackPanel Orientation="Horizontal">
<avalonia:Icon FontSize="18"
Value="fa-solid fa-info"
Margin="8,0" />
<TextBlock Text="Drag &amp; drop one of the cards below into ComfyUI to load the workflow"
VerticalAlignment="Center" />
</StackPanel>
</controls1:CommandBarElementContainer>
</controls1:CommandBar.PrimaryCommands>
</controls1:CommandBar>
<ScrollViewer Grid.Column="0"
Grid.Row="1">
<ItemsRepeater ItemsSource="{Binding DisplayedWorkflows}">
<ItemsRepeater.Layout>
<!-- <UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4"/> -->
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="{x:Type models:OpenArtMetadata}">
<Border
Name="HoverBorder"
Padding="0"
BorderThickness="0"
Margin="8"
ClipToBounds="True"
CornerRadius="8">
<Interaction.Behaviors>
<BehaviorCollection>
<controls:BetterContextDragBehavior
Context="{Binding FilePath}"
DataFormat="Files"
HorizontalDragThreshold="6"
VerticalDragThreshold="6" />
</BehaviorCollection>
</Interaction.Behaviors>
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}"
IsVisible="{Binding HasMetadata}"
Command="{StaticResource OpenOnOpenArtCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon>
<fluent:SymbolIcon Symbol="Open" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static lang:Resources.Action_OpenInExplorer}"
Command="{StaticResource OpenInExplorerCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon>
<fluent:SymbolIcon Symbol="Folder" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static lang:Resources.Action_Delete}"
Command="{StaticResource DeleteCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon>
<fluent:SymbolIcon Symbol="Delete" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<Button
Name="ModelCard"
Classes="transparent-full"
Padding="0"
BorderThickness="0"
VerticalContentAlignment="Top"
CornerRadius="8">
<Grid RowDefinitions="*, Auto">
<labs:AsyncImage
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
Width="330"
Height="400"
Source="{Binding FirstThumbnail}"
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"
Stretch="UniformToFill" />
<avalonia:Icon Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
Width="330"
Height="400"
FontSize="100"
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNull}, FallbackValue=False}"
Value="fa-regular fa-file-code" />
<!-- Username pill card -->
<Border
BoxShadow="inset 1.2 0 80 1.8 #66000000"
CornerRadius="16"
Margin="4"
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom">
<Border.Resources>
<DropShadowEffect
x:Key="TextDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.9" />
<DropShadowEffect
x:Key="ImageDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.2" />
</Border.Resources>
<Button
Command="{x:Static helpers:IOCommands.OpenUrlCommand}"
CommandParameter="{Binding Workflow.Creator.DevProfileUrl}"
CornerRadius="16"
Classes="transparent"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<labs:AsyncImage
Width="22"
Height="22"
Effect="{StaticResource ImageDropShadowEffect}"
CornerRadius="11"
RenderOptions.BitmapInterpolationMode="HighQuality"
IsVisible="{Binding Workflow.Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Source="{Binding Workflow.Creator.Avatar}" />
<TextBlock
VerticalAlignment="Center"
Foreground="{DynamicResource TextControlForeground}"
Effect="{StaticResource TextDropShadowEffect}"
Text="{Binding Workflow.Creator.Name}" />
</StackPanel>
</Button>
</Border>
<Border
Name="ModelCardBottom"
Grid.Row="1">
<TextBlock
Padding="16"
Margin="8,0,8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="{DynamicResource TextControlForeground}"
LetterSpacing="0.33"
TextWrapping="Wrap"
MaxWidth="315"
Text="{Binding Workflow.Name}"
ToolTip.Tip="{Binding Workflow.Name}" />
</Border>
</Grid>
</Button>
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</Grid>
</controls:UserControlBase>

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

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

4
StabilityMatrix.Avalonia/Views/MainWindow.axaml

@ -70,11 +70,13 @@
Grid.RowSpan="2"
Name="NavigationView"
ItemInvoked="NavigationView_OnItemInvoked"
BackRequested="NavigationView_OnBackRequested"
PaneDisplayMode="Left"
IsPaneOpen="False"
OpenPaneLength="{Binding PaneWidth}"
IsSettingsVisible="False"
IsBackEnabled="False"
IsBackEnabled="True"
IsBackButtonVisible="True"
MenuItemsSource="{Binding Pages, Mode=OneWay}"
FooterMenuItemsSource="{Binding FooterPages, Mode=OneWay}"
SelectedItem="{Binding SelectedCategory}">

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

@ -446,4 +446,9 @@ public partial class MainWindow : AppWindowBase
e.Handled = true;
navigationService.GoBack();
}
private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e)
{
navigationService.GoBack();
}
}

360
StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml

@ -0,0 +1,360 @@
<controls:UserControlBase xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:openArt="clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt;assembly=StabilityMatrix.Core"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
x:DataType="viewModels:OpenArtBrowserViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="StabilityMatrix.Avalonia.Views.OpenArtBrowserPage">
<UserControl.Styles>
<Style Selector="Border#HoverBorder">
<Setter Property="Transitions">
<Transitions>
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237" />
</Transitions>
</Setter>
<Style Selector="^ labs|AsyncImage">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform"
Duration="0:0:0.237">
<TransformOperationsTransition.Easing>
<QuadraticEaseInOut />
</TransformOperationsTransition.Easing>
</TransformOperationsTransition>
</Transitions>
</Setter>
</Style>
<Style Selector="^:pointerover">
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000" />
<Setter Property="Cursor" Value="Hand" />
<Style Selector="^ labs|AsyncImage">
<Setter Property="CornerRadius" Value="12" />
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)" />
</Style>
<Style Selector="^ Border#ModelCardBottom">
<Setter Property="Background" Value="#CC000000" />
</Style>
</Style>
<Style Selector="^:not(:pointerover)">
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000" />
<Setter Property="Cursor" Value="Arrow" />
<Style Selector="^ labs|AsyncImage">
<Setter Property="CornerRadius" Value="8" />
<Setter Property="RenderTransform" Value="scale(1, 1)" />
</Style>
<Style Selector="^ Border#ModelCardBottom">
<Setter Property="Background" Value="#99000000" />
</Style>
</Style>
</Style>
</UserControl.Styles>
<UserControl.Resources>
<input:StandardUICommand
x:Key="OpenModelCommand"
Command="{Binding OpenModelCommand}" />
<input:StandardUICommand
x:Key="OpenOnOpenArtCommand"
Command="{Binding OpenOnOpenArtCommand}" />
<input:StandardUICommand
x:Key="OpenWorkflowCommand"
Command="{Binding OpenWorkflowCommand}" />
<converters:KiloFormatterStringConverter x:Key="KiloFormatterConverter" />
<DataTemplate x:Key="OpenArtWorkflowTemplate" DataType="{x:Type openArt:OpenArtSearchResult}">
<Border
Name="HoverBorder"
Padding="0"
BorderThickness="0"
Margin="8"
ClipToBounds="True"
CornerRadius="8">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}"
Command="{StaticResource OpenOnOpenArtCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Open" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<Button
Name="ModelCard"
Classes="transparent-full"
Padding="0"
BorderThickness="0"
VerticalContentAlignment="Top"
CornerRadius="8"
Command="{StaticResource OpenWorkflowCommand}"
CommandParameter="{Binding }">
<Grid RowDefinitions="*, Auto">
<labs:AsyncImage
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
Width="330"
Height="400"
Source="{Binding Thumbnails[0].Url}"
Stretch="UniformToFill" />
<!-- Username pill card -->
<Border
BoxShadow="inset 1.2 0 80 1.8 #66000000"
CornerRadius="16"
Margin="4"
Grid.Row="0"
ClipToBounds="True"
HorizontalAlignment="Left"
VerticalAlignment="Bottom">
<Border.Resources>
<DropShadowEffect
x:Key="TextDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.9" />
<DropShadowEffect
x:Key="ImageDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.2" />
</Border.Resources>
<Button
Command="{x:Static helpers:IOCommands.OpenUrlCommand}"
CommandParameter="{Binding Creator.DevProfileUrl}"
CornerRadius="16"
Classes="transparent"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<labs:AsyncImage
Width="22"
Height="22"
ClipToBounds="True"
Effect="{StaticResource ImageDropShadowEffect}"
CornerRadius="11"
RenderOptions.BitmapInterpolationMode="HighQuality"
IsVisible="{Binding Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Source="{Binding Creator.Avatar}">
</labs:AsyncImage>
<TextBlock
VerticalAlignment="Center"
Foreground="{DynamicResource TextControlForeground}"
Effect="{StaticResource TextDropShadowEffect}"
Text="{Binding Creator.Name}" />
</StackPanel>
</Button>
</Border>
<Border
Name="ModelCardBottom"
Grid.Row="1">
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto, Auto, Auto">
<!--
TextTrimming causing issues with unicode chars until
https://github.com/AvaloniaUI/Avalonia/pull/13385 is released
-->
<TextBlock
Grid.ColumnSpan="2"
MaxWidth="250"
Margin="8,0,8,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="{DynamicResource TextControlForeground}"
LetterSpacing="0.33"
Text="{Binding Name}"
TextWrapping="NoWrap"
ToolTip.Tip="{Binding Name}" />
<StackPanel
Grid.Row="2"
Grid.Column="0"
Orientation="Horizontal">
<controls:StarsRating
Margin="8,8,0,8"
Background="#66000000"
FontSize="16"
Foreground="{DynamicResource ThemeEldenRingOrangeColor}"
Value="{Binding Stats.Rating}" />
<TextBlock
Margin="4,0,0,0"
VerticalAlignment="Center"
Text="{Binding Stats.NumReviews}"
TextAlignment="Center" />
</StackPanel>
<StackPanel
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Right"
Orientation="Horizontal">
<avalonia:Icon Value="fa-solid fa-heart" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
Text="{Binding Stats.NumLikes, Converter={StaticResource KiloFormatterConverter}}" />
<avalonia:Icon Margin="4,0" Value="fa-solid fa-download" />
<TextBlock
Margin="0,0,4,0"
VerticalAlignment="Center"
Text="{Binding Stats.NumDownloads, Converter={StaticResource KiloFormatterConverter}}" />
</StackPanel>
<Button
Grid.Row="0"
Grid.Column="1"
Width="32"
Margin="0,4,4,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Top"
BorderThickness="0"
Classes="transparent">
<ui:SymbolIcon FontSize="18" Symbol="MoreVertical" />
<Button.Flyout>
<MenuFlyout>
<MenuItem Command="{StaticResource OpenModelCommand}"
CommandParameter="{Binding }"
Header="{x:Static lang:Resources.Action_OpenOnOpenArt}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Open" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Button.Flyout>
</Button>
</Grid>
</Border>
</Grid>
</Button>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid RowDefinitions="Auto, Auto, *, Auto">
<Grid Grid.Row="0" ColumnDefinitions="*,Auto"
Margin="8">
<TextBox
HorizontalAlignment="Stretch"
Text="{Binding SearchQuery, Mode=TwoWay}"
Watermark="{x:Static lang:Resources.Action_Search}"
Classes="search"/>
<Button
Grid.Column="1"
Width="80"
Margin="8,0,8,0"
VerticalAlignment="Stretch"
Classes="accent"
Command="{Binding SearchButtonCommand}"
IsDefault="True">
<Grid>
<controls:ProgressRing
MinWidth="16"
MinHeight="16"
VerticalAlignment="Center"
BorderThickness="4"
IsIndeterminate="True"
IsVisible="{Binding SearchButtonCommand.IsRunning}" />
<TextBlock
VerticalAlignment="Center"
IsVisible="{Binding !SearchButtonCommand.IsRunning}"
Text="{x:Static lang:Resources.Action_Search}" />
</Grid>
</Button>
</Grid>
<StackPanel Grid.Row="1"
Margin="8,0,0,8"
Orientation="Vertical"
IsVisible="{Binding SearchQuery, Converter={x:Static StringConverters.IsNullOrEmpty}}">
<Label Content="{x:Static lang:Resources.Label_Sort}" />
<ComboBox
MinWidth="100"
ItemsSource="{Binding AllSortModes}"
SelectedItem="{Binding SelectedSortMode}"/>
</StackPanel>
<controls:ProgressRing Grid.Row="2"
IsVisible="{Binding IsLoading}"
IsIndeterminate="True"
Width="128"
Height="128"/>
<ScrollViewer Grid.Row="2"
ScrollChanged="ScrollViewer_OnScrollChanged"
IsVisible="{Binding !IsLoading}">
<ItemsRepeater ItemsSource="{Binding SearchResults}"
ItemTemplate="{StaticResource OpenArtWorkflowTemplate}">
<ItemsRepeater.Layout>
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4"/>
</ItemsRepeater.Layout>
</ItemsRepeater>
</ScrollViewer>
<StackPanel Grid.Row="3"
HorizontalAlignment="Center"
Margin="0,8,0,8"
Orientation="Horizontal">
<Button
Margin="0,0,8,0"
Command="{Binding FirstPageCommand}"
IsEnabled="{Binding CanGoBack}"
ToolTip.Tip="{x:Static lang:Resources.Label_FirstPage}">
<avalonia:Icon Value="fa-solid fa-backward-fast" />
</Button>
<Button
Margin="0,0,16,0"
Command="{Binding PreviousPageCommand}"
IsEnabled="{Binding CanGoBack}"
ToolTip.Tip="{x:Static lang:Resources.Label_PreviousPage}">
<avalonia:Icon Value="fa-solid fa-caret-left" />
</Button>
<TextBlock Margin="8,0,4,0" TextAlignment="Center"
Text="{x:Static lang:Resources.Label_Page}"
VerticalAlignment="Center"/>
<ui:NumberBox Value="{Binding DisplayedPageNumber, FallbackValue=1}"
VerticalAlignment="Center"
SpinButtonPlacementMode="Hidden"
TextAlignment="Center"/>
<TextBlock Margin="4,0,8,0" VerticalAlignment="Center">
<Run Text="/"/>
<Run Text="{Binding PageCount, FallbackValue=5}"/>
</TextBlock>
<Button
Margin="16,0,8,0"
Command="{Binding NextPageCommand}"
IsEnabled="{Binding CanGoForward}"
ToolTip.Tip="{x:Static lang:Resources.Label_NextPage}">
<avalonia:Icon Value="fa-solid fa-caret-right" />
</Button>
<Button
Command="{Binding LastPageCommand}"
IsEnabled="{Binding CanGoToEnd}"
ToolTip.Tip="{x:Static lang:Resources.Label_LastPage}">
<avalonia:Icon Value="fa-solid fa-forward-fast" />
</Button>
</StackPanel>
</Grid>
</controls:UserControlBase>

41
StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs

@ -0,0 +1,41 @@
using System;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.Views;
[Singleton]
public partial class OpenArtBrowserPage : UserControlBase
{
private readonly ISettingsManager settingsManager;
public OpenArtBrowserPage(ISettingsManager settingsManager)
{
this.settingsManager = settingsManager;
InitializeComponent();
}
private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)
{
if (sender is not ScrollViewer scrollViewer)
return;
if (scrollViewer.Offset.Y == 0)
return;
var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f;
if (
isAtEnd
&& settingsManager.Settings.IsWorkflowInfiniteScrollEnabled
&& DataContext is IInfinitelyScroll scroll
)
{
scroll.LoadNextPageAsync().SafeFireAndForget();
}
}
}

4
StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallBrowserView.axaml

@ -34,8 +34,8 @@
<Grid ColumnDefinitions="Auto, *, Auto">
<controls:BetterAdvancedImage
Grid.Column="0"
MaxHeight="128"
MaxWidth="128"
Height="128"
Width="128"
Stretch="UniformToFill"
CornerRadius="4"
Source="{Binding PreviewImageUri}" />

59
StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
@ -10,8 +9,8 @@
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:database="clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core"
xmlns:extensions="clr-namespace:StabilityMatrix.Core.Models.Packages.Extensions;assembly=StabilityMatrix.Core"
xmlns:packageManager="clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="packageManager:PackageInstallDetailViewModel"
x:CompileBindings="True"
@ -21,13 +20,15 @@
<Grid ColumnDefinitions="Auto, *"
RowDefinitions="Auto, *">
<controls:BetterAdvancedImage
MaxHeight="300"
MaxWidth="300"
Margin="16,16,8,8"
CornerRadius="8"
Height="300"
Width="300"
VerticalAlignment="Top"
Stretch="UniformToFill"
Source="{Binding SelectedPackage.PreviewImageUri}" />
Margin="16, 16, 8, 8"
CornerRadius="8"
Source="{Binding SelectedPackage.PreviewImageUri}">
</controls:BetterAdvancedImage>
<StackPanel Grid.Row="0" Grid.Column="1"
Margin="8"
Orientation="Vertical">
@ -102,15 +103,10 @@
FontSize="16"
HorizontalAlignment="Stretch"
Margin="8">
<Grid ColumnDefinitions="*, *, *"
RowDefinitions="Auto, Auto"
Margin="8,0">
<Label Grid.Row="0" Grid.Column="0"
Content="{x:Static lang:Resources.Label_SharedModelFolderStrategy}"
Margin="0,0,0,4" />
<controls1:FAComboBox Grid.Row="1" Grid.Column="0"
ItemsSource="{Binding SelectedPackage.AvailableSharedFolderMethods}"
<WrapPanel Margin="8,0">
<StackPanel Orientation="Vertical" Margin="0,4,24,0">
<Label Content="{x:Static lang:Resources.Label_SharedModelStrategyShort}" />
<controls1:FAComboBox ItemsSource="{Binding SelectedPackage.AvailableSharedFolderMethods}"
MinWidth="150"
MinHeight="38"
SelectedItem="{Binding SelectedSharedFolderMethod}">
@ -122,13 +118,12 @@
</DataTemplate>
</controls1:FAComboBox.ItemTemplate>
</controls1:FAComboBox>
</StackPanel>
<Label Grid.Row="0" Grid.Column="1"
Content="{x:Static lang:Resources.Label_PyTorchVersion}"
IsVisible="{Binding ShowTorchVersionOptions}"
Margin="0,0,0,4" />
<controls1:FAComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding SelectedPackage.AvailableTorchVersions}"
<StackPanel Orientation="Vertical" Margin="0,4,24,0">
<Label Content="{x:Static lang:Resources.Label_PyTorchVersion}"
IsVisible="{Binding ShowTorchVersionOptions}"/>
<controls1:FAComboBox ItemsSource="{Binding SelectedPackage.AvailableTorchVersions}"
MinWidth="150"
MinHeight="38"
IsVisible="{Binding ShowTorchVersionOptions}"
@ -141,13 +136,12 @@
</DataTemplate>
</controls1:FAComboBox.ItemTemplate>
</controls1:FAComboBox>
</StackPanel>
<Label Grid.Row="0" Grid.Column="2"
Content="{x:Static lang:Resources.Label_Commit}"
Margin="0,0,0,4"
<StackPanel Orientation="Vertical" Margin="0,4,24,0">
<Label Content="{x:Static lang:Resources.Label_Commit}"
IsVisible="{Binding !IsReleaseMode}" />
<controls1:FAComboBox Grid.Row="1" Grid.Column="2"
IsVisible="{Binding !IsReleaseMode}"
<controls1:FAComboBox IsVisible="{Binding !IsReleaseMode}"
ItemsSource="{Binding AvailableCommits}"
MinWidth="150"
MinHeight="38"
@ -161,7 +155,16 @@
</DataTemplate>
</controls1:FAComboBox.ItemTemplate>
</controls1:FAComboBox>
</Grid>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,4,24,0"
IsVisible="{Binding !!SelectedPackage.SharedOutputFolders}">
<Label Content="{x:Static lang:Resources.Label_UseSharedOutputFolder}"/>
<ToggleSwitch VerticalAlignment="Center"
VerticalContentAlignment="Center"
IsChecked="{Binding IsOutputSharingEnabled}"/>
</StackPanel>
</WrapPanel>
</Expander>
<Button Classes="success"

6
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -13,6 +13,7 @@
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:avalonia="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:markupExtensions="clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="450"
x:DataType="viewModels:PackageManagerViewModel"
x:CompileBindings="True"
@ -44,15 +45,13 @@
</MultiBinding>
</controls:Card.Background>
<Grid ColumnDefinitions="Auto, *">
<controls:BetterAdvancedImage
<labs:AsyncImage
Grid.Column="0"
Height="150"
Width="150"
Margin="4, 8"
CornerRadius="8"
HorizontalAlignment="Center"
VerticalContentAlignment="Top"
HorizontalContentAlignment="Center"
Source="{Binding CardImageSource}"
IsVisible="{Binding !IsUnknownPackage}"
Stretch="UniformToFill" />
@ -221,6 +220,7 @@
</MenuItem>
<Separator />
<MenuItem Header="{x:Static lang:Resources.Action_Uninstall}"
IsEnabled="{Binding !IsRunning}"
Command="{Binding Uninstall}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Delete"/>

200
StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml

@ -45,77 +45,6 @@
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="8,16" Spacing="8">
<!-- Theme -->
<Grid RowDefinitions="Auto,*,*,*">
<TextBlock
Margin="0,0,0,8"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_Appearance}" />
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0,8,4"
Header="{x:Static lang:Resources.Label_Theme}"
IconSource="WeatherMoon">
<ui:SettingsExpander.Footer>
<ComboBox
MinWidth="100"
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander
Grid.Row="2"
Margin="8,0,8,4"
Header="{x:Static lang:Resources.Label_Language}"
IconSource="Character">
<ui:SettingsExpander.Footer>
<ComboBox
MinWidth="100"
DisplayMemberBinding="{Binding Converter={StaticResource CultureInfoDisplayConverter}}"
ItemsSource="{Binding AvailableLanguages}"
SelectedItem="{Binding SelectedLanguage}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander
Grid.Row="3"
Margin="8,0,8,4"
ActionIconSource="ChevronRight"
Command="{Binding NavigateToSubPageCommand}"
CommandParameter="{x:Type vmSettings:InferenceSettingsViewModel}"
Header="Inference (Test)"
IconSource="Code"
IsClickEnabled="True"
IsVisible="{Binding SharedState.IsDebugMode}" />
</Grid>
<!-- Checkpoints Manager Options -->
<Grid RowDefinitions="auto,*,Auto">
<TextBlock
Margin="0,0,0,8"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_CheckpointManager}" />
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0"
Description="{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown_Details}"
Header="{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown}"
IconSource="Folder">
<ui:SettingsExpander.Footer>
<CheckBox IsChecked="{Binding RemoveSymlinksOnShutdown}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander
Grid.Row="2"
Margin="8,4"
Description="{x:Static lang:Resources.Label_ResetCheckpointsCache_Details}"
Header="{x:Static lang:Resources.Label_ResetCheckpointsCache}"
IconSource="Refresh">
<ui:SettingsExpander.Footer>
<Button Command="{Binding ResetCheckpointCache}" Content="{x:Static lang:Resources.Label_ResetCheckpointsCache}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Grid>
<!-- General -->
<sg:SpacedGrid RowDefinitions="Auto,*,*,*" RowSpacing="4">
<TextBlock
@ -168,6 +97,68 @@
</ui:SettingsExpander>
</sg:SpacedGrid>
<!-- Integrations -->
<sg:SpacedGrid RowDefinitions="Auto,*,*" RowSpacing="4">
<TextBlock
Margin="0,0,0,4"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_Integrations}" />
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0"
ActionIconSource="ChevronRight"
Command="{Binding NavigateToSubPageCommand}"
CommandParameter="{x:Type vmSettings:AccountSettingsViewModel}"
Header="{x:Static lang:Resources.Label_Accounts}"
IsClickEnabled="True">
<ui:SettingsExpander.IconSource>
<fluentIcons:SymbolIconSource
FontSize="10"
IsFilled="True"
Symbol="Person" />
</ui:SettingsExpander.IconSource>
</ui:SettingsExpander>
<ui:SettingsExpander
Grid.Row="2"
Margin="8,0,8,4"
Header="{x:Static lang:Resources.Label_DiscordRichPresence}">
<ui:SettingsExpander.IconSource>
<controls:FASymbolIconSource Symbol="fa-brands fa-discord" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding IsDiscordRichPresenceEnabled}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</sg:SpacedGrid>
<!-- Checkpoints Manager Options -->
<Grid RowDefinitions="auto,*,Auto">
<TextBlock
Margin="0,0,0,8"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_CheckpointManager}" />
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0"
Description="{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown_Details}"
Header="{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown}"
IconSource="Folder">
<ui:SettingsExpander.Footer>
<CheckBox IsChecked="{Binding RemoveSymlinksOnShutdown}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander
Grid.Row="2"
Margin="8,4"
Description="{x:Static lang:Resources.Label_ResetCheckpointsCache_Details}"
Header="{x:Static lang:Resources.Label_ResetCheckpointsCache}"
IconSource="Refresh">
<ui:SettingsExpander.Footer>
<Button Command="{Binding ResetCheckpointCache}" Content="{x:Static lang:Resources.Label_ResetCheckpointsCache}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Grid>
<!-- Environment Options -->
<Grid RowDefinitions="Auto, Auto, Auto">
<TextBlock
@ -205,39 +196,66 @@
</ui:SettingsExpander>
</Grid>
<!-- Integrations -->
<sg:SpacedGrid RowDefinitions="Auto,*,*" RowSpacing="4">
<sg:SpacedGrid RowDefinitions="Auto, Auto">
<TextBlock
Margin="0,0,0,4"
Margin="0,0,0,8"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_Integrations}" />
Text="{x:Static lang:Resources.Label_WorkflowBrowser}" />
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0"
ActionIconSource="ChevronRight"
Command="{Binding NavigateToSubPageCommand}"
CommandParameter="{x:Type vmSettings:AccountSettingsViewModel}"
Header="{x:Static lang:Resources.Label_Accounts}"
IsClickEnabled="True">
Margin="8,0,8,4"
Header="{x:Static lang:Resources.Label_InfiniteScrolling}">
<ui:SettingsExpander.IconSource>
<fluentIcons:SymbolIconSource
FontSize="10"
IsFilled="True"
Symbol="Person" />
<controls:FASymbolIconSource Symbol="fa-solid fa-list" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding InfinitelyScrollWorkflowBrowser}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</sg:SpacedGrid>
<!-- Theme -->
<Grid RowDefinitions="Auto,*,*,*">
<TextBlock
Margin="0,0,0,8"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_Appearance}" />
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0,8,4"
Header="{x:Static lang:Resources.Label_Theme}"
IconSource="WeatherMoon">
<ui:SettingsExpander.Footer>
<ComboBox
MinWidth="100"
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander
Grid.Row="2"
Margin="8,0,8,4"
Header="{x:Static lang:Resources.Label_DiscordRichPresence}">
<ui:SettingsExpander.IconSource>
<controls:FASymbolIconSource Symbol="fa-brands fa-discord" />
</ui:SettingsExpander.IconSource>
Header="{x:Static lang:Resources.Label_Language}"
IconSource="Character">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding IsDiscordRichPresenceEnabled}" />
<ComboBox
MinWidth="100"
DisplayMemberBinding="{Binding Converter={StaticResource CultureInfoDisplayConverter}}"
ItemsSource="{Binding AvailableLanguages}"
SelectedItem="{Binding SelectedLanguage}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</sg:SpacedGrid>
<ui:SettingsExpander
Grid.Row="3"
Margin="8,0,8,4"
ActionIconSource="ChevronRight"
Command="{Binding NavigateToSubPageCommand}"
CommandParameter="{x:Type vmSettings:InferenceSettingsViewModel}"
Header="Inference (Test)"
IconSource="Code"
IsClickEnabled="True"
IsVisible="{Binding SharedState.IsDebugMode}" />
</Grid>
<!-- System Options -->
<sg:SpacedGrid RowDefinitions="Auto,Auto,Auto,Auto,Auto" RowSpacing="4">

12
StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml

@ -0,0 +1,12 @@
<controls:UserControlBase xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:WorkflowsPageViewModel"
x:Class="StabilityMatrix.Avalonia.Views.WorkflowsPage">
<TabControl ItemsSource="{Binding Pages}"
SelectedItem="{Binding SelectedPage}"/>
</controls:UserControlBase>

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

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

17
StabilityMatrix.Core/Api/IOpenArtApi.cs

@ -0,0 +1,17 @@
using Refit;
using StabilityMatrix.Core.Models.Api.OpenArt;
namespace StabilityMatrix.Core.Api;
[Headers("User-Agent: StabilityMatrix")]
public interface IOpenArtApi
{
[Get("/feed")]
Task<OpenArtSearchResponse> GetFeedAsync([Query] OpenArtFeedRequest request);
[Get("/list")]
Task<OpenArtSearchResponse> SearchAsync([Query] OpenArtSearchRequest request);
[Post("/download")]
Task<OpenArtDownloadResponse> DownloadWorkflowAsync([Body] OpenArtDownloadRequest request);
}

11
StabilityMatrix.Core/Extensions/ProgressExtensions.cs

@ -7,16 +7,19 @@ namespace StabilityMatrix.Core.Extensions;
public static class ProgressExtensions
{
[return: NotNullIfNotNull(nameof(progress))]
public static Action<ProcessOutput>? AsProcessOutputHandler(
this IProgress<ProgressReport>? progress
)
public static Action<ProcessOutput>? AsProcessOutputHandler(this IProgress<ProgressReport>? progress)
{
return progress == null
? null
: output =>
{
progress.Report(
new ProgressReport { IsIndeterminate = true, Message = output.Text }
new ProgressReport
{
IsIndeterminate = true,
Message = output.Text,
ProcessOutput = output
}
);
};
}

3
StabilityMatrix.Core/Helper/EventManager.cs

@ -46,6 +46,7 @@ public class EventManager
public event EventHandler<int>? NavigateAndFindCivitModelRequested;
public event EventHandler? DownloadsTeachingTipRequested;
public event EventHandler? RecommendedModelsDialogClosed;
public event EventHandler? WorkflowInstalled;
public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress);
@ -108,4 +109,6 @@ public class EventManager
public void OnPackageRelaunchRequested(InstalledPackage package) =>
PackageRelaunchRequested?.Invoke(this, package);
public void OnWorkflowInstalled() => WorkflowInstalled?.Invoke(this, EventArgs.Empty);
}

23
StabilityMatrix.Core/Helper/Utilities.cs

@ -1,8 +1,9 @@
using System.Reflection;
using System.Text.RegularExpressions;
namespace StabilityMatrix.Core.Helper;
public static class Utilities
public static partial class Utilities
{
public static string GetAppVersion()
{
@ -63,4 +64,24 @@ public static class Utilities
return stream;
}
public static string RemoveHtml(string? stringWithHtml)
{
var pruned =
stringWithHtml
?.Replace("<br/>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("<br />", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</p>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h1>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h2>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h3>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h4>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h5>", $"{Environment.NewLine}{Environment.NewLine}")
.Replace("</h6>", $"{Environment.NewLine}{Environment.NewLine}") ?? string.Empty;
pruned = HtmlRegex().Replace(pruned, string.Empty);
return pruned;
}
[GeneratedRegex("<[^>]+>")]
private static partial Regex HtmlRegex();
}

4
StabilityMatrix.Core/Models/Api/CivitImage.cs

@ -7,8 +7,8 @@ public class CivitImage
[JsonPropertyName("url")]
public string Url { get; set; }
[JsonPropertyName("nsfw")]
public string Nsfw { get; set; }
[JsonPropertyName("nsfwLevel")]
public int? NsfwLevel { get; set; }
[JsonPropertyName("width")]
public int Width { get; set; }

15
StabilityMatrix.Core/Models/Api/OpenArt/NodesCount.cs

@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class NodesCount
{
[JsonPropertyName("total")]
public long Total { get; set; }
[JsonPropertyName("primitive")]
public long Primitive { get; set; }
[JsonPropertyName("custom")]
public long Custom { get; set; }
}

24
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtCreator.cs

@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtCreator
{
[JsonPropertyName("uid")]
public string Uid { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("bio")]
public string Bio { get; set; }
[JsonPropertyName("avatar")]
public Uri Avatar { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("dev_profile_url")]
public string DevProfileUrl { get; set; }
}

14
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDateTime.cs

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtDateTime
{
[JsonPropertyName("_seconds")]
public long Seconds { get; set; }
public DateTimeOffset ToDateTimeOffset()
{
return DateTimeOffset.FromUnixTimeSeconds(Seconds);
}
}

15
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadRequest.cs

@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
using Refit;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtDownloadRequest
{
[AliasAs("workflow_id")]
[JsonPropertyName("workflow_id")]
public required string WorkflowId { get; set; }
[AliasAs("version_tag")]
[JsonPropertyName("version_tag")]
public string VersionTag { get; set; } = "latest";
}

12
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadResponse.cs

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtDownloadResponse
{
[JsonPropertyName("filename")]
public string Filename { get; set; }
[JsonPropertyName("payload")]
public string Payload { get; set; }
}

21
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtFeedRequest.cs

@ -0,0 +1,21 @@
using Refit;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
/// <summary>
/// Note that parameters Category, Custom Node and Sort should be used separately
/// </summary>
public class OpenArtFeedRequest
{
[AliasAs("category")]
public string Category { get; set; }
[AliasAs("sort")]
public string Sort { get; set; }
[AliasAs("custom_node")]
public string CustomNode { get; set; }
[AliasAs("cursor")]
public string Cursor { get; set; }
}

18
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchRequest.cs

@ -0,0 +1,18 @@
using Refit;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtSearchRequest
{
[AliasAs("keyword")]
public required string Keyword { get; set; }
[AliasAs("pageSize")]
public int PageSize { get; set; } = 30;
/// <summary>
/// 0-based index of the page to retrieve
/// </summary>
[AliasAs("currentPage")]
public int CurrentPage { get; set; } = 0;
}

15
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResponse.cs

@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtSearchResponse
{
[JsonPropertyName("items")]
public IEnumerable<OpenArtSearchResult> Items { get; set; }
[JsonPropertyName("total")]
public int Total { get; set; }
[JsonPropertyName("nextCursor")]
public string? NextCursor { get; set; }
}

33
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResult.cs

@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtSearchResult
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("creator")]
public OpenArtCreator Creator { get; set; }
[JsonPropertyName("stats")]
public OpenArtStats Stats { get; set; }
[JsonPropertyName("nodes_index")]
public IEnumerable<string> NodesIndex { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("categories")]
public IEnumerable<string> Categories { get; set; }
[JsonPropertyName("thumbnails")]
public List<OpenArtThumbnail> Thumbnails { get; set; }
[JsonPropertyName("nodes_count")]
public NodesCount NodesCount { get; set; }
}

33
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtStats.cs

@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtStats
{
[JsonPropertyName("num_shares")]
public int NumShares { get; set; }
[JsonPropertyName("num_bookmarks")]
public int NumBookmarks { get; set; }
[JsonPropertyName("num_reviews")]
public int NumReviews { get; set; }
[JsonPropertyName("rating")]
public double Rating { get; set; }
[JsonPropertyName("num_comments")]
public int NumComments { get; set; }
[JsonPropertyName("num_likes")]
public int NumLikes { get; set; }
[JsonPropertyName("num_downloads")]
public int NumDownloads { get; set; }
[JsonPropertyName("num_runs")]
public int NumRuns { get; set; }
[JsonPropertyName("num_views")]
public int NumViews { get; set; }
}

15
StabilityMatrix.Core/Models/Api/OpenArt/OpenArtThumbnail.cs

@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtThumbnail
{
[JsonPropertyName("width")]
public int Width { get; set; }
[JsonPropertyName("url")]
public Uri Url { get; set; }
[JsonPropertyName("height")]
public int Height { get; set; }
}

8420
StabilityMatrix.Core/Models/ComfyNodeMap.cs

File diff suppressed because it is too large Load Diff

36
StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs

@ -0,0 +1,36 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using StabilityMatrix.Core.Api;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.PackageModification;
public class DownloadOpenArtWorkflowStep(
IOpenArtApi openArtApi,
OpenArtSearchResult workflow,
ISettingsManager settingsManager
) : IPackageStep
{
public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)
{
var workflowData = await openArtApi
.DownloadWorkflowAsync(new OpenArtDownloadRequest { WorkflowId = workflow.Id })
.ConfigureAwait(false);
var workflowJson = JsonSerializer.SerializeToNode(workflow);
Directory.CreateDirectory(settingsManager.WorkflowDirectory);
var filePath = Path.Combine(settingsManager.WorkflowDirectory, $"{workflowData.Filename}.json");
var jsonObject = JsonNode.Parse(workflowData.Payload) as JsonObject;
jsonObject?.Add("sm_workflow_data", workflowJson);
await File.WriteAllTextAsync(filePath, JsonSerializer.Serialize(jsonObject)).ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, "Downloaded OpenArt Workflow"));
}
public string ProgressTitle => "Downloading OpenArt Workflow";
}

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

@ -31,7 +31,7 @@ public class InstallPackageStep : IPackageStep
{
void OnConsoleOutput(ProcessOutput output)
{
progress?.Report(new ProgressReport { IsIndeterminate = true, Message = output.Text });
progress?.Report(new ProgressReport { IsIndeterminate = true, ProcessOutput = output });
}
await package

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

@ -0,0 +1,15 @@
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
namespace StabilityMatrix.Core.Models.PackageModification;
public class SetupOutputSharingStep(BasePackage package, string installPath) : IPackageStep
{
public Task ExecuteAsync(IProgress<ProgressReport>? progress = null)
{
package.SetupOutputFolderLinks(installPath);
return Task.CompletedTask;
}
public string ProgressTitle => "Setting up output sharing...";
}

9
StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs

@ -31,7 +31,14 @@ public class UpdatePackageStep : IPackageStep
void OnConsoleOutput(ProcessOutput output)
{
progress?.Report(new ProgressReport { IsIndeterminate = true, Message = output.Text });
progress?.Report(
new ProgressReport
{
IsIndeterminate = true,
Message = output.Text,
ProcessOutput = output
}
);
}
var updateResult = await basePackage

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

@ -213,7 +213,7 @@ public class ComfyUI(
_
=> pipArgs
.AddArg("--upgrade")
.WithTorch("~=2.1.0")
.WithTorch()
.WithTorchVision()
.WithTorchExtraIndex(
torchVersion switch
@ -233,9 +233,6 @@ public class ComfyUI(
switch (torchVersion)
{
case TorchVersion.Cuda:
pipArgs = pipArgs.WithXFormers("==0.0.22.post4");
break;
case TorchVersion.Mps:
pipArgs = pipArgs.AddArg("mpmath==1.3.0");
break;

8
StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs

@ -89,6 +89,14 @@ public interface IPackageExtensionManager
CancellationToken cancellationToken = default
);
/// <summary>
/// Like <see cref="GetInstalledExtensionsAsync"/>, but does not check version.
/// </summary>
Task<IEnumerable<InstalledPackageExtension>> GetInstalledExtensionsLiteAsync(
InstalledPackage installedPackage,
CancellationToken cancellationToken = default
);
/// <summary>
/// Get updated info (version) for an installed extension.
/// </summary>

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

@ -32,23 +32,14 @@ public class InvokeAI : BaseGitPackage
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Nightmare;
public override IReadOnlyList<string> ExtraLaunchCommands =>
new[]
{
"invokeai-configure",
"invokeai-merge",
"invokeai-metadata",
"invokeai-model-install",
"invokeai-node-cli",
"invokeai-ti",
"invokeai-update",
};
new[] { "invokeai-db-maintenance", "invokeai-import-images", };
public override Uri PreviewImageUri =>
new("https://raw.githubusercontent.com/invoke-ai/InvokeAI/main/docs/assets/canvas_preview.png");
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
new[] { SharedFolderMethod.None };
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;
public override string MainBranch => "main";
@ -127,23 +118,11 @@ public class InvokeAI : BaseGitPackage
Options = ["--allow-origins"]
},
new LaunchOptionDefinition
{
Name = "Always use CPU",
Type = LaunchOptionType.Bool,
Options = ["--always_use_cpu"]
},
new LaunchOptionDefinition
{
Name = "Precision",
Type = LaunchOptionType.Bool,
Options = ["--precision auto", "--precision float16", "--precision float32"]
},
new LaunchOptionDefinition
{
Name = "Aggressively free up GPU memory after each operation",
Type = LaunchOptionType.Bool,
Options = ["--free_gpu_mem"]
},
LaunchOptionDefinition.Extras
];
@ -223,9 +202,9 @@ public class InvokeAI : BaseGitPackage
await venvRunner
.PipInstall(
new PipInstallArgs(args.Any() ? args.ToArray() : Array.Empty<Argument>())
.WithTorch("==2.1.2")
.WithTorchVision("==0.16.2")
.WithXFormers("==0.0.23post1")
.WithTorch("==2.2.1")
.WithTorchVision("==0.17.1")
.WithXFormers("==0.0.25")
.WithTorchExtraIndex("cu121"),
onConsoleOutput
)
@ -262,24 +241,6 @@ public class InvokeAI : BaseGitPackage
.ConfigureAwait(false);
await venvRunner.PipInstall("rich packaging python-dotenv", onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Configuring InvokeAI", isIndeterminate: true));
// need to setup model links before running invokeai-configure so it can do its conversion
await SetupModelFolders(installLocation, selectedSharedFolderMethod).ConfigureAwait(false);
await RunInvokeCommand(
installLocation,
"invokeai-configure",
"--yes --skip-sd-weights",
true,
onConsoleOutput,
spam3: true
)
.ConfigureAwait(false);
await VenvRunner.Process.WaitForExitAsync();
progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false));
}
@ -362,12 +323,6 @@ public class InvokeAI : BaseGitPackage
await SetupVenv(installedPackagePath).ConfigureAwait(false);
arguments = command switch
{
"invokeai-configure" => "--yes --skip-sd-weights",
_ => arguments
};
VenvRunner.EnvironmentVariables = GetEnvVars(installedPackagePath);
// fix frontend build missing for people who updated to v3.6 before the fix

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

@ -132,32 +132,19 @@ public class KohyaSs(
await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false);
// Extra dep needed before running setup since v23.0.x
await venvRunner.PipInstall("packaging").ConfigureAwait(false);
await venvRunner.PipInstall(["rich", "packaging"]).ConfigureAwait(false);
if (Compat.IsWindows)
{
var setupSmPath = Path.Combine(installLocation, "setup", "setup_sm.py");
var setupText = """
import setup_windows
import setup_common
setup_common.install_requirements('requirements_windows_torch2.txt', check_no_verify_flag=False)
setup_windows.sync_bits_and_bytes_files()
setup_common.configure_accelerate(run_accelerate=False)
""";
await File.WriteAllTextAsync(setupSmPath, setupText).ConfigureAwait(false);
// Install
await venvRunner.CustomInstall("setup/setup_sm.py", onConsoleOutput).ConfigureAwait(false);
await venvRunner.PipInstall("bitsandbytes-windows").ConfigureAwait(false);
await venvRunner
.CustomInstall("setup/setup_windows.py --headless", onConsoleOutput)
.ConfigureAwait(false);
}
else if (Compat.IsLinux)
{
await venvRunner
.CustomInstall(
"setup/setup_linux.py --platform-requirements-file=requirements_linux.txt --no_run_accelerate",
onConsoleOutput
)
.CustomInstall("setup/setup_linux.py --headless", onConsoleOutput)
.ConfigureAwait(false);
}
}

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

@ -124,10 +124,10 @@ public class RuinedFooocus(
await venvRunner
.PipInstall(
new PipInstallArgs()
.WithTorch("==2.0.1")
.WithTorchVision("==0.15.2")
.WithXFormers("==0.0.20")
.WithTorchExtraIndex("cu118")
.WithTorch("==2.1.2")
.WithTorchVision("==0.16.2")
.WithXFormers("==0.0.23.post1")
.WithTorchExtraIndex("cu121")
.WithParsedFromRequirementsTxt(
await requirements.ReadAllTextAsync().ConfigureAwait(false),
excludePattern: "torch"

46
StabilityMatrix.Core/Models/Progress/ProgressReport.cs

@ -1,4 +1,6 @@
namespace StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Core.Models.Progress;
public record struct ProgressReport
{
@ -6,21 +8,30 @@ public record struct ProgressReport
/// Progress value as percentage between 0 and 1.
/// </summary>
public double? Progress { get; init; } = 0;
/// <summary>
/// Current progress count.
/// </summary>
public ulong? Current { get; init; } = 0;
/// <summary>
/// Total progress count.
/// </summary>
public ulong? Total { get; init; } = 0;
public string? Title { get; init; }
public string? Message { get; init; }
public ProcessOutput? ProcessOutput { get; init; }
public bool IsIndeterminate { get; init; } = false;
public float Percentage => (float)Math.Ceiling(Math.Clamp(Progress ?? 0, 0, 1) * 100);
public ProgressType Type { get; init; } = ProgressType.Generic;
public ProgressReport(double progress, string? title = null, string? message = null, bool isIndeterminate = false, ProgressType type = ProgressType.Generic)
public ProgressReport(
double progress,
string? title = null,
string? message = null,
bool isIndeterminate = false,
ProgressType type = ProgressType.Generic
)
{
Progress = progress;
Title = title;
@ -29,7 +40,14 @@ public record struct ProgressReport
Type = type;
}
public ProgressReport(ulong current, ulong total, string? title = null, string? message = null, bool isIndeterminate = false, ProgressType type = ProgressType.Generic)
public ProgressReport(
ulong current,
ulong total,
string? title = null,
string? message = null,
bool isIndeterminate = false,
ProgressType type = ProgressType.Generic
)
{
Current = current;
Total = total;
@ -40,10 +58,19 @@ public record struct ProgressReport
Type = type;
}
public ProgressReport(int current, int total, string? title = null, string? message = null, bool isIndeterminate = false, ProgressType type = ProgressType.Generic)
public ProgressReport(
int current,
int total,
string? title = null,
string? message = null,
bool isIndeterminate = false,
ProgressType type = ProgressType.Generic
)
{
if (current < 0) throw new ArgumentOutOfRangeException(nameof(current), "Current progress cannot negative.");
if (total < 0) throw new ArgumentOutOfRangeException(nameof(total), "Total progress cannot be negative.");
if (current < 0)
throw new ArgumentOutOfRangeException(nameof(current), "Current progress cannot negative.");
if (total < 0)
throw new ArgumentOutOfRangeException(nameof(total), "Total progress cannot be negative.");
Current = (ulong)current;
Total = (ulong)total;
Progress = (double)current / total;
@ -53,7 +80,12 @@ public record struct ProgressReport
Type = type;
}
public ProgressReport(ulong current, string? title = null, string? message = null, ProgressType type = ProgressType.Generic)
public ProgressReport(
ulong current,
string? title = null,
string? message = null,
ProgressType type = ProgressType.Generic
)
{
Current = current;
Title = title;

14
StabilityMatrix.Core/Models/Settings/Settings.cs

@ -34,6 +34,19 @@ public class Settings
set => ActiveInstalledPackageId = value?.Id;
}
[JsonPropertyName("PreferredWorkflowPackage")]
public Guid? PreferredWorkflowPackageId { get; set; }
[JsonIgnore]
public InstalledPackage? PreferredWorkflowPackage
{
get =>
PreferredWorkflowPackageId == null
? null
: InstalledPackages.FirstOrDefault(x => x.Id == PreferredWorkflowPackageId);
set => PreferredWorkflowPackageId = value?.Id;
}
public bool HasSeenWelcomeNotification { get; set; }
public List<string>? PathExtensions { get; set; }
public string? WebApiHost { get; set; }
@ -120,6 +133,7 @@ public class Settings
public Size InferenceImageSize { get; set; } = new(150, 190);
public Size OutputsImageSize { get; set; } = new(300, 300);
public HolidayMode HolidayModeSetting { get; set; } = HolidayMode.Automatic;
public bool IsWorkflowInfiniteScrollEnabled { get; set; } = true;
public bool IsOutputsTreeViewEnabled { get; set; } = true;
[JsonIgnore]

2
StabilityMatrix.Core/Python/PyVenvRunner.cs

@ -214,7 +214,7 @@ public class PyVenvRunner : IDisposable, IAsyncDisposable
});
SetPyvenvCfg(PyRunner.PythonDir);
RunDetached(args.Prepend("-m pip install"), outputAction);
RunDetached(args.Prepend("-m pip install").Concat("--exists-action s"), outputAction);
await Process.WaitForExitAsync().ConfigureAwait(false);
// Check return code

1
StabilityMatrix.Core/Services/ISettingsManager.cs

@ -22,6 +22,7 @@ public interface ISettingsManager
Settings Settings { get; }
List<string> PackageInstallsInProgress { get; set; }
DirectoryPath WorkflowDirectory { get; }
/// <summary>
/// Event fired when the library directory is changed

1
StabilityMatrix.Core/Services/SettingsManager.cs

@ -64,6 +64,7 @@ public class SettingsManager : ISettingsManager
private FilePath SettingsFile => LibraryDir.JoinFile("settings.json");
public string ModelsDirectory => Path.Combine(LibraryDir, "Models");
public string DownloadsDirectory => Path.Combine(LibraryDir, ".downloads");
public DirectoryPath WorkflowDirectory => LibraryDir.JoinDir("Workflows");
public DirectoryPath TagsDirectory => LibraryDir.JoinDir("Tags");
public DirectoryPath ImagesDirectory => LibraryDir.JoinDir("Images");
public DirectoryPath ImagesInferenceDirectory => ImagesDirectory.JoinDir("Inference");

12
StabilityMatrix.Core/StabilityMatrix.Core.csproj

@ -17,8 +17,8 @@
<ItemGroup>
<PackageReference Include="AsyncAwaitBestPractices" Version="7.0.0" />
<PackageReference Include="AutoCtor" Version="2.1.1" />
<PackageReference Include="Blake3" Version="1.0.0" />
<PackageReference Include="AutoCtor" Version="2.2.0" />
<PackageReference Include="Blake3" Version="1.1.0" />
<PackageReference Include="Crc32.NET" Version="1.2.0" />
<PackageReference Include="CSharpDiscriminatedUnion" Version="2.0.1" />
<PackageReference Include="DeviceId" Version="6.6.0" />
@ -27,10 +27,10 @@
<PackageReference Include="DeviceId.Windows" Version="6.6.0" />
<PackageReference Include="DeviceId.Windows.Wmi" Version="6.6.0" />
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
<PackageReference Include="DynamicData" Version="8.3.27" />
<PackageReference Include="DynamicData" Version="8.4.1" />
<PackageReference Include="ExifLibNet" Version="2.1.4" />
<PackageReference Include="FreneticLLC.FreneticUtilities" Version="1.0.24" />
<PackageReference Include="Hardware.Info" Version="100.0.1.1" />
<PackageReference Include="Hardware.Info" Version="100.1.0" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="KGySoft.Drawing.Core" Version="8.1.0" />
<PackageReference Include="KGySoft.Drawing.SkiaSharp" Version="8.1.0" />
@ -45,7 +45,7 @@
<PackageReference Include="Octokit" Version="10.0.0" />
<PackageReference Include="OneOf" Version="3.0.263" />
<PackageReference Include="OneOf.SourceGenerator" Version="3.0.263" />
<PackageReference Include="Polly" Version="8.3.0" />
<PackageReference Include="Polly" Version="8.3.1" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageReference Include="pythonnet" Version="3.0.3" />
<PackageReference Include="Refit" Version="7.0.0" />
@ -53,7 +53,7 @@
<PackageReference Include="RockLib.Reflection.Optimized" Version="3.0.0" />
<PackageReference Include="Salaros.ConfigParser" Version="0.3.8" />
<PackageReference Include="Semver" Version="3.0.0-beta.1" />
<PackageReference Include="Sentry.NLog" Version="4.1.2" />
<PackageReference Include="Sentry.NLog" Version="4.2.1" />
<PackageReference Include="SharpCompress" Version="0.36.0" />
<PackageReference Include="SkiaSharp" Version="2.88.7" />
<PackageReference Include="Websocket.Client" Version="5.1.1" />

8
StabilityMatrix.Tests/StabilityMatrix.Tests.csproj

@ -17,18 +17,18 @@
<ItemGroup>
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
<PackageReference Include="DotNext" Version="5.1.0" />
<PackageReference Include="DotNext" Version="5.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="Polly" Version="8.3.0" />
<PackageReference Include="Polly" Version="8.3.1" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
</ItemGroup>

8
StabilityMatrix.UITests/StabilityMatrix.UITests.csproj

@ -15,16 +15,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.0.9" />
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.0.10" />
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="System.Drawing.Common" Version="8.0.2" />
<PackageReference Include="Verify" Version="23.2.2" />
<PackageReference Include="System.Drawing.Common" Version="8.0.3" />
<PackageReference Include="Verify" Version="23.5.2" />
<PackageReference Include="Verify.CommunityToolkit.Mvvm" Version="0.1.0" />
<PackageReference Include="Verify.Avalonia" Version="1.0.1" />
<PackageReference Include="Verify.Phash" Version="3.1.0" />
<PackageReference Include="Verify.Xunit" Version="23.2.2" />
<PackageReference Include="Verify.Xunit" Version="23.5.2" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Loading…
Cancel
Save