Browse Source

Merge branch 'dev' into inference-preprocessors

pull/629/head
Ionite 9 months ago committed by GitHub
parent
commit
75be254457
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 33
      CHANGELOG.md
  2. 1
      StabilityMatrix.Avalonia/App.axaml
  3. 3
      StabilityMatrix.Avalonia/App.axaml.cs
  4. 23
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  5. 126
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  6. 4
      StabilityMatrix.Avalonia/Languages/Resources.de.resx
  7. 181
      StabilityMatrix.Avalonia/Languages/Resources.es.resx
  8. 38
      StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
  9. 31
      StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx
  10. 24
      StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx
  11. 42
      StabilityMatrix.Avalonia/Languages/Resources.resx
  12. 58
      StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
  13. 5
      StabilityMatrix.Avalonia/Models/PackageOutputCategory.cs
  14. 148
      StabilityMatrix.Avalonia/Services/RunningPackageService.cs
  15. 2
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  16. 73
      StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml
  17. 403
      StabilityMatrix.Avalonia/Styles/CommandBarButtonStyles.axaml
  18. 1
      StabilityMatrix.Avalonia/Styles/ThemeColors.axaml
  19. 12
      StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
  20. 16
      StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs
  21. 3
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
  22. 12
      StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs
  23. 3
      StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
  24. 15
      StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs
  25. 9
      StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
  26. 20
      StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs
  27. 5
      StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs
  28. 15
      StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs
  29. 5
      StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs
  30. 13
      StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs
  31. 117
      StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs
  32. 8
      StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
  33. 3
      StabilityMatrix.Avalonia/ViewModels/NewCheckpointsPageViewModel.cs
  34. 7
      StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs
  35. 55
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  36. 271
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  37. 2
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
  38. 15
      StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
  39. 161
      StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs
  40. 93
      StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml
  41. 54
      StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs
  42. 46
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
  43. 110
      StabilityMatrix.Avalonia/Views/OutputsPage.axaml
  44. 276
      StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallBrowserView.axaml
  45. 595
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml
  46. 33
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml.cs
  47. 4
      StabilityMatrix.Core/Helper/EventManager.cs
  48. 1
      StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs
  49. 74
      StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
  50. 3
      StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs
  51. 1
      StabilityMatrix.Core/Models/Settings/Settings.cs
  52. 1
      StabilityMatrix.Core/Models/Settings/TeachingTip.cs
  53. 15
      StabilityMatrix.Tests/Helper/PackageFactoryTests.cs

33
CHANGELOG.md

@ -5,6 +5,39 @@ 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-dev.1
### Changed
- Revamped the Packages page to enable running multiple packages at the same time
- Changed the Outputs Page to use a TreeView for the directory selection instead of a dropdown selector
### Removed
- Removed the main Launch page, as it is no longer needed with the new Packages page
## v2.9.0
### Added
- Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI
- Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel
- Added extension management for SD.Next and Stable Diffusion WebUI-UX
- Added the ability to choose where CivitAI model downloads are saved
- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`)
- Added more Base Model search options to the CivitAI Model Browser
- Added Stable Cascade to the HuggingFace Model Browser
#### Inference
- Added Inference Prompt Styles, with Prompt Expansion model support (i.e. Fooocus V2)
- Added option to load a .yaml config file next to the model with the same name. Can be used with VPred and other models that require a config file.
- Added copy image support on linux and macOS for Inference outputs viewer menu
### Changed
- Updated translations for German, Spanish, French, Japanese, Portuguese, and Turkish
- (Internal) Updated to Avalonia 11.0.9
### Fixed
- Fixed StableSwarmUI not installing properly on macOS
- Fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS
- Fixed [#335](https://github.com/LykosAI/StabilityMatrix/issues/335) Update hanging indefinitely after git step for Auto1111 and SDWebUI Forge
- Fixed Inference output viewer menu "Copy" not copying image
- Fixed image viewer dialog arrow key navigation not working
- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins
- Fixed unknown model types not showing on checkpoints page (thanks Jerry!)
- Improved error handling for Inference Select Image hash calculation in case file is being written to while being read
## v2.9.0-pre.2
### Added
- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`)

1
StabilityMatrix.Avalonia/App.axaml

@ -54,6 +54,7 @@
<StyleInclude Source="Styles/DockStyles.axaml"/>
<StyleInclude Source="Styles/BorderStyles.axaml"/>
<StyleInclude Source="Styles/TextBoxStyles.axaml"/>
<StyleInclude Source="Styles/CommandBarButtonStyles.axaml"/>
<StyleInclude Source="Controls/AdvancedImageBox.axaml"/>
<StyleInclude Source="Controls/FrameCarousel.axaml"/>
<StyleInclude Source="Controls/CodeCompletion/CompletionWindow.axaml"/>

3
StabilityMatrix.Avalonia/App.axaml.cs

@ -328,9 +328,8 @@ public sealed class App : Application
{
Pages =
{
provider.GetRequiredService<LaunchPageViewModel>(),
provider.GetRequiredService<InferenceViewModel>(),
provider.GetRequiredService<NewPackageManagerViewModel>(),
provider.GetRequiredService<InferenceViewModel>(),
provider.GetRequiredService<CheckpointsPageViewModel>(),
provider.GetRequiredService<CheckpointBrowserViewModel>(),
provider.GetRequiredService<OutputsPageViewModel>()

23
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -98,6 +98,19 @@ public static class DesignData
},
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now
},
new()
{
Id = Guid.NewGuid(),
DisplayName = "Running Comfy",
PackageName = "ComfyUI",
Version = new InstalledPackageVersion
{
InstalledBranch = "master",
InstalledCommitSha = "abc12uwu345568972abaedf7g7e679a98879e879f87ga8"
},
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now
}
},
ActiveInstalledPackageId = activePackageId
@ -507,6 +520,16 @@ public static class DesignData
}
)
};
vm.Categories =
[
new PackageOutputCategory
{
Name = "Category 1",
Path = "path1",
SubDirectories = [new PackageOutputCategory { Name = "SubCategory 1", Path = "path3" }]
},
new PackageOutputCategory { Name = "Category 2", Path = "path2" }
];
return vm;
}
}

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

@ -680,6 +680,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to This action cannot be undone..
/// </summary>
public static string Label_ActionCannotBeUndone {
get {
return ResourceManager.GetString("Label_ActionCannotBeUndone", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Addons.
/// </summary>
@ -752,6 +761,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to delete {0} images?.
/// </summary>
public static string Label_AreYouSureDeleteImages {
get {
return ResourceManager.GetString("Label_AreYouSureDeleteImages", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Augmentation Level.
/// </summary>
@ -860,6 +878,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to We&apos;re checking some hardware specifications to determine compatibility..
/// </summary>
public static string Label_CheckingHardware {
get {
return ResourceManager.GetString("Label_CheckingHardware", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Checkpoint Manager.
/// </summary>
@ -869,6 +896,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Checkpoints.
/// </summary>
public static string Label_Checkpoints {
get {
return ResourceManager.GetString("Label_Checkpoints", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CivitAI.
/// </summary>
@ -968,6 +1004,24 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Confirm Exit.
/// </summary>
public static string Label_ConfirmExit {
get {
return ResourceManager.GetString("Label_ConfirmExit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to exit? This will also close any currently running packages..
/// </summary>
public static string Label_ConfirmExitDetail {
get {
return ResourceManager.GetString("Label_ConfirmExitDetail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Confirm Password.
/// </summary>
@ -1013,6 +1067,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Console.
/// </summary>
public static string Label_Console {
get {
return ResourceManager.GetString("Label_Console", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This will move all generated images from the selected packages to the Consolidated directory of the shared outputs folder. This action cannot be undone..
/// </summary>
@ -1238,6 +1301,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Everything looks good!.
/// </summary>
public static string Label_EverythingLooksGood {
get {
return ResourceManager.GetString("Label_EverythingLooksGood", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You may encounter errors when using a FAT32 or exFAT drive. Select a different drive for a smoother experience..
/// </summary>
@ -1598,6 +1670,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Model Browser.
/// </summary>
public static string Label_ModelBrowser {
get {
return ResourceManager.GetString("Label_ModelBrowser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Model Description.
/// </summary>
@ -1724,6 +1805,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to We recommend a GPU with CUDA support for the best experience. You can continue without one, but some packages may not work, and inference may be slower..
/// </summary>
public static string Label_NvidiaGpuRecommended {
get {
return ResourceManager.GetString("Label_NvidiaGpuRecommended", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1 image selected.
/// </summary>
@ -1787,6 +1877,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Packages.
/// </summary>
public static string Label_Packages {
get {
return ResourceManager.GetString("Label_Packages", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Package Type.
/// </summary>
@ -2255,6 +2354,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Auto-Scroll to End.
/// </summary>
public static string Label_ToggleAutoScrolling {
get {
return ResourceManager.GetString("Label_ToggleAutoScrolling", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Trigger words:.
/// </summary>
@ -2399,6 +2507,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Web UI.
/// </summary>
public static string Label_WebUi {
get {
return ResourceManager.GetString("Label_WebUi", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Width.
/// </summary>
@ -2543,6 +2660,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;Open Web UI&apos; button has moved to the command bar.
/// </summary>
public static string TeachingTip_WebUiButtonMoved {
get {
return ResourceManager.GetString("TeachingTip_WebUiButtonMoved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The app will relaunch after updating.
/// </summary>

4
StabilityMatrix.Avalonia/Languages/Resources.de.resx

@ -521,6 +521,7 @@
</data>
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve">
<value>Wählen Sie Ihre bevorzugte Schnittstelle und klicken Sie auf Installieren, um loszulegen.</value>
<comment>Fuzzy</comment>
</data>
<data name="Label_Installing" xml:space="preserve">
<value>Installiert</value>
@ -777,6 +778,9 @@
<data name="Label_ApiKey" xml:space="preserve">
<value>API Schlüssel</value>
</data>
<data name="Label_Accounts" xml:space="preserve">
<value>Accounts</value>
</data>
<data name="Label_Preprocessor" xml:space="preserve">
<value>Preprocessor</value>
</data>

181
StabilityMatrix.Avalonia/Languages/Resources.es.resx

@ -522,6 +522,7 @@
</data>
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve">
<value>Elige tu interfaz preferida y pulsa Instalar para comenzar</value>
<comment>Fuzzy</comment>
</data>
<data name="Label_Installing" xml:space="preserve">
<value>Instalando</value>
@ -782,4 +783,184 @@
<data name="Label_Accounts" xml:space="preserve">
<value>Cuentas</value>
</data>
<data name="Label_Preprocessor" xml:space="preserve">
<value>Preprocesador</value>
</data>
<data name="Label_Strength" xml:space="preserve">
<value>Fortaleza</value>
</data>
<data name="Label_ControlWeight" xml:space="preserve">
<value>Control de Peso</value>
</data>
<data name="Label_ControlSteps" xml:space="preserve">
<value>Control de Pasos</value>
</data>
<data name="Label_CivitAiLoginRequired" xml:space="preserve">
<value>Debes iniciar sesión para descargar este checkpoint. Ingresa una clave API de CivitAI en la configuración.</value>
</data>
<data name="Label_DownloadFailed" xml:space="preserve">
<value>Descarga Fallida</value>
</data>
<data name="Label_AutoUpdates" xml:space="preserve">
<value>Actualizaciones automáticas</value>
</data>
<data name="Label_UpdatesPreviewChannelDescription" xml:space="preserve">
<value>Para los usuarios primerizos. Las versiones preliminares serán más fiables que las del canal de desarrollo y estarán disponibles antes de las versiones estables. Tus comentarios nos ayudarán enormemente a descubrir problemas y pulir elementos de diseño.</value>
</data>
<data name="Label_UpdatesDevChannelDescription" xml:space="preserve">
<value>Para usuarios técnicos. Sé el primero en acceder a nuestras compilaciones de desarrollo desde las ramas de funciones tan pronto como estén disponibles. Es posible que haya algunas asperezas y errores a medida que experimentamos con nuevas funciones.</value>
</data>
<data name="Label_Updates" xml:space="preserve">
<value>Actualizaciones</value>
</data>
<data name="Label_YouAreUpToDate" xml:space="preserve">
<value>Estás al día</value>
</data>
<data name="TextTemplate_LastChecked" xml:space="preserve">
<value>Última comprobación: {0}</value>
</data>
<data name="Action_CopyTriggerWords" xml:space="preserve">
<value>Copiar palabras desencadenantes</value>
</data>
<data name="Label_TriggerWords" xml:space="preserve">
<value>Palabras desencadenantes:</value>
</data>
<data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve">
<value>Aquí se pueden habilitar carpetas adicionales como IPAdapters y TextualInversions (embeddings)</value>
</data>
<data name="Action_OpenOnHuggingFace" xml:space="preserve">
<value>Abrir en Hugging Face</value>
</data>
<data name="Action_UpdateExistingMetadata" xml:space="preserve">
<value>Actualizar metadatos existentes</value>
</data>
<data name="Label_General" xml:space="preserve">
<value>General</value>
<comment>A general settings category</comment>
</data>
<data name="Label_Inference" xml:space="preserve">
<value>Inferencia</value>
<comment>The Inference feature page</comment>
</data>
<data name="Label_Prompt" xml:space="preserve">
<value>Prompt</value>
<comment>A settings category for Inference generation prompts</comment>
</data>
<data name="Label_OutputImageFiles" xml:space="preserve">
<value>Archivos de imágenes resultantes</value>
</data>
<data name="Label_ImageViewer" xml:space="preserve">
<value>Visor de Imágenes</value>
</data>
<data name="Label_AutoCompletion" xml:space="preserve">
<value>Auto Completado</value>
</data>
<data name="Label_CompletionReplaceUnderscoresWithSpaces" xml:space="preserve">
<value>Reemplazar guiones bajos con espacios al insertar terminaciones</value>
</data>
<data name="Label_PromptTags" xml:space="preserve">
<value>Etiquetas de Prompt</value>
<comment>Tags for image generation prompts</comment>
</data>
<data name="Label_PromptTagsImport" xml:space="preserve">
<value>Importar Etiquetas de Prompt</value>
</data>
<data name="Label_PromptTagsDescription" xml:space="preserve">
<value>Archivo de etiquetas que se utilizarán para sugerir terminaciones (admite el formato .csv a1111-sd-webui-tagcomplete)</value>
</data>
<data name="Label_SystemInformation" xml:space="preserve">
<value>Información del Sistema</value>
</data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
<data name="Label_HuggingFace" xml:space="preserve">
<value>Hugging Face</value>
</data>
<data name="Label_Addons" xml:space="preserve">
<value>Complementos</value>
<comment>Inference Sampler Addons</comment>
</data>
<data name="Label_SaveIntermediateImage" xml:space="preserve">
<value>Guardar Imagen Intermedia</value>
<comment>Inference module step to save an intermediate image</comment>
</data>
<data name="Label_Settings" xml:space="preserve">
<value>Ajustes</value>
</data>
<data name="Action_SelectFile" xml:space="preserve">
<value>Seleccionar Archivo</value>
</data>
<data name="Action_ReplaceContents" xml:space="preserve">
<value>Reemplazar contenido</value>
</data>
<data name="Label_WipFeature" xml:space="preserve">
<value>No disponible aún</value>
</data>
<data name="Label_WipFeatureDescription" xml:space="preserve">
<value>La función estará disponible en una actualización futura</value>
</data>
<data name="Label_MissingImageFile" xml:space="preserve">
<value>Archivo de Imagen Faltante</value>
</data>
<data name="Label_HolidayMode" xml:space="preserve">
<value>Modo Vacaciones</value>
</data>
<data name="Label_CLIPSkip" xml:space="preserve">
<value>Saltar CLIP</value>
</data>
<data name="Label_ImageToVideo" xml:space="preserve">
<value>Imagen a Vídeo</value>
</data>
<data name="Label_Fps" xml:space="preserve">
<value>Frames por Segundo</value>
</data>
<data name="Label_MinCfg" xml:space="preserve">
<value>CFG Mínimo</value>
</data>
<data name="Label_Lossless" xml:space="preserve">
<value>Sin pérdidas</value>
</data>
<data name="Label_Frames" xml:space="preserve">
<value>Frames</value>
</data>
<data name="Label_MotionBucketId" xml:space="preserve">
<value>Motion Bucket ID</value>
</data>
<data name="Label_AugmentationLevel" xml:space="preserve">
<value>Nivel de aumento</value>
</data>
<data name="Label_VideoOutputMethod" xml:space="preserve">
<value>Método</value>
</data>
<data name="Label_VideoQuality" xml:space="preserve">
<value>Calidad</value>
</data>
<data name="Label_FindInModelBrowser" xml:space="preserve">
<value>Buscar en el navegador de modelos</value>
</data>
<data name="Label_Installed" xml:space="preserve">
<value>Instalado</value>
</data>
<data name="Label_NoExtensionsFound" xml:space="preserve">
<value>No se encuentran extensiones.</value>
</data>
<data name="Action_Hide" xml:space="preserve">
<value>Ocultar</value>
</data>
<data name="Action_CopyDetails" xml:space="preserve">
<value>Copiar detalles</value>
</data>
<data name="Action_Download" xml:space="preserve">
<value>Descargar</value>
</data>
<data name="TeachingTip_DownloadsExplanation" xml:space="preserve">
<value>Verifica el progreso de instalación de tus paquetes y descargas de modelos aquí.</value>
</data>
<data name="Label_RecommendedModels" xml:space="preserve">
<value>Modelos recomendados</value>
</data>
<data name="Label_RecommendedModelsSubText" xml:space="preserve">
<value>Mientras se instala tu paquete, aquí hay algunos modelos que recomendamos para ayudarte a comenzar.</value>
</data>
</root>

38
StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx

@ -319,7 +319,7 @@
<value>Recherche</value>
</data>
<data name="Label_Sort" xml:space="preserve">
<value>Trier</value>
<value>Tri</value>
</data>
<data name="Label_TimePeriod" xml:space="preserve">
<value>Période</value>
@ -810,6 +810,9 @@
<data name="TextTemplate_LastChecked" xml:space="preserve">
<value>Dernière vérification: {0}</value>
</data>
<data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve">
<value>Les dossiers additionnels comme IPAdapters ou TextualInversions (embeddings) peuvent être activés ici</value>
</data>
<data name="Action_OpenOnHuggingFace" xml:space="preserve">
<value>Ouvrir sur Hugging Face</value>
</data>
@ -834,6 +837,9 @@
<data name="Label_AutoCompletion" xml:space="preserve">
<value>Auto-complétion</value>
</data>
<data name="Label_CompletionReplaceUnderscoresWithSpaces" xml:space="preserve">
<value>Remplacer les underscores par des espaces lors de l&apos;auto-complétion.</value>
</data>
<data name="Label_SystemInformation" xml:space="preserve">
<value>Informations système</value>
</data>
@ -888,10 +894,40 @@
<data name="Label_MotionBucketId" xml:space="preserve">
<value>Motion Bucket ID</value>
</data>
<data name="Label_AugmentationLevel" xml:space="preserve">
<value>Niveau d&apos;augmentation</value>
</data>
<data name="Label_VideoOutputMethod" xml:space="preserve">
<value>Méthode</value>
</data>
<data name="Label_VideoQuality" xml:space="preserve">
<value>Qualité</value>
</data>
<data name="Label_FindInModelBrowser" xml:space="preserve">
<value>Trouver dans l&apos;explorateur de modèle</value>
</data>
<data name="Label_Installed" xml:space="preserve">
<value>Installé</value>
</data>
<data name="Label_NoExtensionsFound" xml:space="preserve">
<value>Pas d&apos;extensions trouvés.</value>
</data>
<data name="Action_Hide" xml:space="preserve">
<value>Masquer</value>
</data>
<data name="Action_CopyDetails" xml:space="preserve">
<value>Copier les détails</value>
</data>
<data name="Action_Download" xml:space="preserve">
<value>Télécharger</value>
</data>
<data name="TeachingTip_DownloadsExplanation" xml:space="preserve">
<value>Vérifiez la progression de l&apos;installation de votre paquet ainsi que le téléchargement de votre modèle ici.</value>
</data>
<data name="Label_RecommendedModels" xml:space="preserve">
<value>Modèles recommandés</value>
</data>
<data name="Label_RecommendedModelsSubText" xml:space="preserve">
<value>Pendant que votre paquet s&apos;installe, voici quelques modèles que nous recommandons pour vous aidez à démarrer.</value>
</data>
</root>

31
StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx

@ -530,6 +530,7 @@
</data>
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve">
<value>好きなUIをインストールして始めよう</value>
<comment>Fuzzy</comment>
</data>
<data name="Label_Installing" xml:space="preserve">
<value>インストール中</value>
@ -706,6 +707,24 @@
<data name="Action_Refresh" xml:space="preserve">
<value>更新</value>
</data>
<data name="Action_OpenGithub" xml:space="preserve">
<value>GitHubで開く</value>
</data>
<data name="Label_Username" xml:space="preserve">
<value>ユーザ名</value>
</data>
<data name="Label_Password" xml:space="preserve">
<value>パスワード</value>
</data>
<data name="Action_Login" xml:space="preserve">
<value>ログイン</value>
</data>
<data name="Action_Signup" xml:space="preserve">
<value>サインアップ</value>
</data>
<data name="Label_ConfirmPassword" xml:space="preserve">
<value>パスワード再確認</value>
</data>
<data name="Label_CivitAiLoginRequired" xml:space="preserve">
<value>ダウンロードにはCivitAIのログインが必要です。SettingからAPIキーを入力してください。</value>
</data>
@ -715,6 +734,9 @@
<data name="Label_UpdatesDevChannelDescription" xml:space="preserve">
<value>Devビルドはテクニカルなユーザ向けです。新機能をいち早く利用できます。荒削りな部分やバグがあるかもしれません。</value>
</data>
<data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve">
<value>IPAdaptersやTextualInversions(embeddings)などのフォルダは、ここで有効にできます。</value>
</data>
<data name="Action_OpenOnHuggingFace" xml:space="preserve">
<value>Hugging Faceで開く</value>
</data>
@ -734,4 +756,13 @@
<data name="Label_Lossless" xml:space="preserve">
<value>非圧縮</value>
</data>
<data name="TeachingTip_DownloadsExplanation" xml:space="preserve">
<value>Packageのインストール・Modelのダウンロードの進捗状況はこちらで確認できます。</value>
</data>
<data name="Label_RecommendedModels" xml:space="preserve">
<value>推奨Model</value>
</data>
<data name="Label_RecommendedModelsSubText" xml:space="preserve">
<value>Packageのインストール中に、推奨Modelをいくつかご紹介しましょう。</value>
</data>
</root>

24
StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx

@ -196,7 +196,7 @@
<value>Passos - Refinamento</value>
</data>
<data name="Label_CFGScale" xml:space="preserve">
<value>Escale de CFG</value>
<value>Escala de CFG</value>
</data>
<data name="Label_DenoisingStrength" xml:space="preserve">
<value>Nível de Denoising</value>
@ -391,7 +391,7 @@
<value>Pasta de Modelos</value>
</data>
<data name="Label_Categories" xml:space="preserve">
<value>Catagorias</value>
<value>Categorias</value>
</data>
<data name="Label_LetsGetStarted" xml:space="preserve">
<value>Vamos começar!</value>
@ -409,7 +409,7 @@
<value>Exibir imagens dos Modelos</value>
</data>
<data name="Label_Appearance" xml:space="preserve">
<value>Aparencia</value>
<value>Aparência</value>
</data>
<data name="Label_Theme" xml:space="preserve">
<value>Tema</value>
@ -521,6 +521,7 @@
</data>
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve">
<value>Escolha sua interface preferida e clique em Instalar para começar</value>
<comment>Fuzzy</comment>
</data>
<data name="Label_Installing" xml:space="preserve">
<value>Instalando</value>
@ -840,7 +841,7 @@
<comment>The Inference feature page</comment>
</data>
<data name="Label_Prompt" xml:space="preserve">
<value>Incitar</value>
<value>Prompt</value>
<comment>A settings category for Inference generation prompts</comment>
</data>
<data name="Label_OutputImageFiles" xml:space="preserve">
@ -853,17 +854,17 @@
<value>Preenchimento automático</value>
</data>
<data name="Label_CompletionReplaceUnderscoresWithSpaces" xml:space="preserve">
<value>Substitua sublinhados por espaços ao inserir conclusões</value>
<value>Substitua caracteres sublinhados por espaços ao inserir conclusões</value>
</data>
<data name="Label_PromptTags" xml:space="preserve">
<value>Etiquetas de prompt</value>
<value>Etiquetas de Prompt</value>
<comment>Tags for image generation prompts</comment>
</data>
<data name="Label_PromptTagsImport" xml:space="preserve">
<value>Tags de prompt de importação</value>
<value>Importar Tags de Prompt</value>
</data>
<data name="Label_PromptTagsDescription" xml:space="preserve">
<value>Arquivo de tags a ser usado para sugerir conclusões (suporta o formato .csv a1111-sd-webui-tagcomplete)</value>
<value>Arquivo de tags a ser usado para Sugestão de Completions (suporta o formato .csv a1111-sd-webui-tagcomplete)</value>
</data>
<data name="Label_SystemInformation" xml:space="preserve">
<value>Informação do sistema</value>
@ -872,7 +873,7 @@
<value>CivitAI</value>
</data>
<data name="Label_HuggingFace" xml:space="preserve">
<value>Abraçando o rosto</value>
<value>Hugging Face</value>
</data>
<data name="Label_Addons" xml:space="preserve">
<value>Complementos</value>
@ -901,7 +902,7 @@
<value>Arquivo de imagem não encontrado</value>
</data>
<data name="Label_HolidayMode" xml:space="preserve">
<value>Modo Férias</value>
<value>Modo Feriado</value>
</data>
<data name="Label_CLIPSkip" xml:space="preserve">
<value>Pular CLIPE</value>
@ -930,4 +931,7 @@
<data name="Label_VideoOutputMethod" xml:space="preserve">
<value>Método</value>
</data>
<data name="Label_VideoQuality" xml:space="preserve">
<value>Qualidade</value>
</data>
</root>

42
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -978,4 +978,46 @@
<data name="Action_PreviewPreprocessor" xml:space="preserve">
<value>Preview Preprocessor</value>
</data>
<data name="Label_ToggleAutoScrolling" xml:space="preserve">
<value>Auto-Scroll to End</value>
</data>
<data name="Label_ConfirmExit" xml:space="preserve">
<value>Confirm Exit</value>
</data>
<data name="Label_ConfirmExitDetail" xml:space="preserve">
<value>Are you sure you want to exit? This will also close any currently running packages.</value>
</data>
<data name="Label_Console" xml:space="preserve">
<value>Console</value>
</data>
<data name="Label_WebUi" xml:space="preserve">
<value>Web UI</value>
</data>
<data name="Label_Packages" xml:space="preserve">
<value>Packages</value>
</data>
<data name="Label_ActionCannotBeUndone" xml:space="preserve">
<value>This action cannot be undone.</value>
</data>
<data name="Label_AreYouSureDeleteImages" xml:space="preserve">
<value>Are you sure you want to delete {0} images?</value>
</data>
<data name="Label_CheckingHardware" xml:space="preserve">
<value>We're checking some hardware specifications to determine compatibility.</value>
</data>
<data name="Label_EverythingLooksGood" xml:space="preserve">
<value>Everything looks good!</value>
</data>
<data name="Label_NvidiaGpuRecommended" xml:space="preserve">
<value>We recommend a GPU with CUDA support for the best experience. You can continue without one, but some packages may not work, and inference may be slower.</value>
</data>
<data name="Label_Checkpoints" xml:space="preserve">
<value>Checkpoints</value>
</data>
<data name="Label_ModelBrowser" xml:space="preserve">
<value>Model Browser</value>
</data>
<data name="TeachingTip_WebUiButtonMoved" xml:space="preserve">
<value>The 'Open Web UI' button has moved to the command bar</value>
</data>
</root>

58
StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx

@ -133,7 +133,7 @@
<value>Dil</value>
</data>
<data name="Text_RelaunchRequiredToApplyLanguage" xml:space="preserve">
<value>Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekiyor</value>
<value>Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekir.</value>
</data>
<data name="Action_Relaunch" xml:space="preserve">
<value>Yeniden başlat</value>
@ -166,7 +166,7 @@
<value>Dallar</value>
</data>
<data name="Label_DragAndDropCheckpointsHereToImport" xml:space="preserve">
<value>İçe aktarmak için chekpoints&apos;leri buraya sürükleyip bırakın</value>
<value>İçe aktarmak için chekpoints&apos;leri buraya sürükleyip bırakın.</value>
</data>
<data name="Label_Emphasis" xml:space="preserve">
<value>Vurgu</value>
@ -235,7 +235,7 @@
<value>Sponsor Ol</value>
</data>
<data name="Label_JoinDiscord" xml:space="preserve">
<value>Discord Sunucusuna Katıl</value>
<value>Discord Sunucumuza Katıl</value>
</data>
<data name="Label_Downloads" xml:space="preserve">
<value>İndirmeler</value>
@ -313,7 +313,7 @@
<value>Tüm Sürümler</value>
</data>
<data name="Label_ModelSearchWatermark" xml:space="preserve">
<value>Modelleri, #etiketleri veya @kullanıcıları ara</value>
<value>Modelleri,Tagları (#tag) veya Kullanıcıları (@user) Buradan Arayabilirsin</value>
</data>
<data name="Action_Search" xml:space="preserve">
<value>Ara</value>
@ -322,13 +322,13 @@
<value>Sırala</value>
</data>
<data name="Label_TimePeriod" xml:space="preserve">
<value>Süre</value>
<value>Sıralama Tarihi</value>
</data>
<data name="Label_ModelType" xml:space="preserve">
<value>Model Türü</value>
</data>
<data name="Label_BaseModel" xml:space="preserve">
<value>Temel Model</value>
<value>Model Altyapısı</value>
</data>
<data name="Label_ShowNsfwContent" xml:space="preserve">
<value>NSFW İçerik Göster</value>
@ -376,10 +376,10 @@
<value>Klasör</value>
</data>
<data name="Label_DropFileToImport" xml:space="preserve">
<value>İçe aktarma için dosyayı buraya bırakın</value>
<value>Dosyayı içeri aktarmak için lütfen sürükleyip işaretlenen alana bırakın.</value>
</data>
<data name="Label_ImportAsConnected" xml:space="preserve">
<value>Metadata ile içeri aktar</value>
<value>Dosyaları metadata ile içeri aktar</value>
</data>
<data name="Label_ImportAsConnectedExplanation" xml:space="preserve">
<value>Yeni yerel içe aktarmalar için bağlı meta veri arayın</value>
@ -448,7 +448,7 @@
<value>Entegrasyonlar</value>
</data>
<data name="Label_DiscordRichPresence" xml:space="preserve">
<value>Discord Zengin Varlık</value>
<value>Discord Etkinliğini StabilityMatrix Olarak Göster.</value>
</data>
<data name="Label_System" xml:space="preserve">
<value>Sistem</value>
@ -469,10 +469,10 @@
<value>Tüm Kullanıcılar İçin Ekle</value>
</data>
<data name="Label_SelectNewDataDirectory" xml:space="preserve">
<value>Yeni Veri Dizini Seç</value>
<value>Yeni Dosya Dizinini Seç</value>
</data>
<data name="Label_SelectNewDataDirectory_Details" xml:space="preserve">
<value>Mevcut veriyi taşımaz</value>
<value>Bu işlem mevcut veriyi yeni dizine taşımaz.</value>
</data>
<data name="Action_SelectDirectory" xml:space="preserve">
<value>Dizin Seçin</value>
@ -490,7 +490,7 @@
<value>Başlamak için Başlat&apos;a tıklayın!</value>
</data>
<data name="Action_Stop" xml:space="preserve">
<value>Dur</value>
<value>Durdur</value>
</data>
<data name="Action_SendInput" xml:space="preserve">
<value>Giriş Gönder</value>
@ -521,6 +521,7 @@
</data>
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve">
<value>Tercih ettiğiniz arayüzü seçin ve başlamak için Yükle&apos;ye tıklayın</value>
<comment>Fuzzy</comment>
</data>
<data name="Label_Installing" xml:space="preserve">
<value>Yükleniyor</value>
@ -694,10 +695,10 @@
<value>{0} resim seçildi</value>
</data>
<data name="Label_OutputFolder" xml:space="preserve">
<value>Çıkış Klasörü</value>
<value>Çıktı Klasörü</value>
</data>
<data name="Label_OutputType" xml:space="preserve">
<value>Çıkış Türü</value>
<value>Çıktı Türü</value>
</data>
<data name="Action_ClearSelection" xml:space="preserve">
<value>Seçimi Temizle</value>
@ -721,7 +722,7 @@
<value>Upscale</value>
</data>
<data name="Label_OutputsPageTitle" xml:space="preserve">
<value>Çıkış Tarayıcısı</value>
<value>Medya Kitaplığı</value>
</data>
<data name="Label_OneImageSelected" xml:space="preserve">
<value>1 resim seçildi</value>
@ -933,4 +934,31 @@
<data name="Label_VideoQuality" xml:space="preserve">
<value>Kalite</value>
</data>
<data name="Label_FindInModelBrowser" xml:space="preserve">
<value>Model Tarayıcıda Bul</value>
</data>
<data name="Label_Installed" xml:space="preserve">
<value>Kurulmuş</value>
</data>
<data name="Label_NoExtensionsFound" xml:space="preserve">
<value>Uzantı bulunamadı.</value>
</data>
<data name="Action_Hide" xml:space="preserve">
<value>Gizle</value>
</data>
<data name="Action_CopyDetails" xml:space="preserve">
<value>Ayrıntıları Kopyala</value>
</data>
<data name="Action_Download" xml:space="preserve">
<value>İndir</value>
</data>
<data name="TeachingTip_DownloadsExplanation" xml:space="preserve">
<value>Paket kurulumlarınızın ve model indirmelerinizin ilerlemesini buradan kontrol edin.</value>
</data>
<data name="Label_RecommendedModels" xml:space="preserve">
<value>Önerilen Modeller</value>
</data>
<data name="Label_RecommendedModelsSubText" xml:space="preserve">
<value>Paketiniz yüklenirken başlamanıza yardımcı olması için önerdiğimiz bazı modeller aşağıda verilmiştir.</value>
</data>
</root>

5
StabilityMatrix.Avalonia/Models/PackageOutputCategory.cs

@ -1,7 +1,10 @@
namespace StabilityMatrix.Avalonia.Models;
using System.Collections.ObjectModel;
namespace StabilityMatrix.Avalonia.Models;
public class PackageOutputCategory
{
public ObservableCollection<PackageOutputCategory> SubDirectories { get; set; } = new();
public required string Name { get; set; }
public required string Path { get; set; }
}

148
StabilityMatrix.Avalonia/Services/RunningPackageService.cs

@ -0,0 +1,148 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.Services;
[Singleton]
public partial class RunningPackageService(
ILogger<RunningPackageService> logger,
IPackageFactory packageFactory,
INotificationService notificationService,
ISettingsManager settingsManager,
IPyRunner pyRunner
) : ObservableObject
{
// 🤔 what if we put the ConsoleViewModel inside the BasePackage? 🤔
[ObservableProperty]
private ObservableDictionary<Guid, RunningPackageViewModel> runningPackages = [];
public async Task<PackagePair?> StartPackage(InstalledPackage installedPackage, string? command = null)
{
var activeInstallName = installedPackage.PackageName;
var basePackage = string.IsNullOrWhiteSpace(activeInstallName)
? null
: packageFactory.GetNewBasePackage(installedPackage);
if (basePackage == null)
{
logger.LogWarning(
"During launch, package name '{PackageName}' did not match a definition",
activeInstallName
);
notificationService.Show(
new Notification(
"Package name invalid",
"Install package name did not match a definition. Please reinstall and let us know about this issue.",
NotificationType.Error
)
);
return null;
}
// If this is the first launch (LaunchArgs is null),
// load and save a launch options dialog vm
// so that dynamic initial values are saved.
if (installedPackage.LaunchArgs == null)
{
var definitions = basePackage.LaunchOptions;
// Create config cards and save them
var cards = LaunchOptionCard
.FromDefinitions(definitions, Array.Empty<LaunchOption>())
.ToImmutableArray();
var args = cards.SelectMany(c => c.Options).ToList();
logger.LogDebug(
"Setting initial launch args: {Args}",
string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr()))
);
settingsManager.SaveLaunchArgs(installedPackage.Id, args);
}
if (basePackage is not StableSwarm)
{
await pyRunner.Initialize();
}
// Get path from package
var packagePath = new DirectoryPath(settingsManager.LibraryDir, installedPackage.LibraryPath!);
if (basePackage is not StableSwarm)
{
// Unpack sitecustomize.py to venv
await UnpackSiteCustomize(packagePath.JoinDir("venv"));
}
// Clear console and start update processing
var console = new ConsoleViewModel();
console.StartUpdates();
// Update shared folder links (in case library paths changed)
await basePackage.UpdateModelFolders(
packagePath,
installedPackage.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod
);
// Load user launch args from settings and convert to string
var userArgs = installedPackage.LaunchArgs ?? [];
var userArgsString = string.Join(" ", userArgs.Select(opt => opt.ToArgString()));
// Join with extras, if any
userArgsString = string.Join(" ", userArgsString, basePackage.ExtraLaunchArguments);
// Use input command if provided, otherwise use package launch command
command ??= basePackage.LaunchCommand;
await basePackage.RunPackage(packagePath, command, userArgsString, o => console.Post(o));
var runningPackage = new PackagePair(installedPackage, basePackage);
var viewModel = new RunningPackageViewModel(
settingsManager,
notificationService,
this,
runningPackage,
console
);
RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel);
return runningPackage;
}
public async Task StopPackage(Guid id)
{
if (RunningPackages.TryGetValue(id, out var vm))
{
var runningPackage = vm.RunningPackage;
await runningPackage.BasePackage.WaitForShutdown();
RunningPackages.Remove(id);
}
}
public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) =>
RunningPackages.TryGetValue(id, out var vm) ? vm : null;
private static async Task UnpackSiteCustomize(DirectoryPath venvPath)
{
var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath);
var file = sitePackages.JoinFile("sitecustomize.py");
file.Directory?.Create();
await Assets.PyScriptSiteCustomize.ExtractTo(file, true);
}
}

2
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -17,7 +17,7 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationIcon>./Assets/Icon.ico</ApplicationIcon>
<Version>2.9.0-dev.999</Version>
<Version>2.10.0-dev.999</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

73
StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml

@ -99,6 +99,43 @@
</Style>
</Style>
<!-- Danger -->
<Style Selector="Button.borderless-danger">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeRedColor}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeDarkRedColor}"/>
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeDarkDarkRedColor}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- Info -->
<Style Selector="Button.info">
<Style Selector="^ /template/ ui|FABorder#Root">
@ -140,6 +177,42 @@
</Style>
</Style>
<Style Selector="Button.borderless-info">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeLightBlueColor}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeBlueColor}"/>
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeDarkBlueColor}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!--Accent Button-->
<Style Selector="Button.accent">
<Style Selector="^ /template/ ui|FABorder#Root">

403
StabilityMatrix.Avalonia/Styles/CommandBarButtonStyles.axaml

@ -0,0 +1,403 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Padding="20">
<ui:CommandBar
Margin="8"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
DefaultLabelPosition="Right">
<ui:CommandBar.PrimaryCommands>
<ui:CommandBarButton Classes="success" Label="Success Button" Margin="8"
HorizontalAlignment="Center" />
<ui:CommandBarButton Classes="accent" Label="FA Accent Button" Margin="8"
HorizontalAlignment="Center" />
<ui:CommandBarButton Classes="systemaccent" Label="System Accent Button" Margin="8"
HorizontalAlignment="Center" />
<ui:CommandBarButton Classes="danger" Label="Danger Button" Margin="8" HorizontalAlignment="Center" />
<ui:CommandBarButton Classes="info" Label="Info Button" Margin="8" HorizontalAlignment="Center" />
<ui:CommandBarButton Classes="transparent-info" Label="Semi-Transparent Info Button" Margin="8"
HorizontalAlignment="Center" />
<ui:CommandBarButton Classes="transparent" Label="Transparent Button" Margin="8"
HorizontalAlignment="Center" />
<ui:CommandBarButton Classes="transparent-full" Label="Transparent Button" Margin="8"
HorizontalAlignment="Center" />
<ui:CommandBarButton Label="Disabled Button" Margin="8" IsEnabled="False"
HorizontalAlignment="Center" />
</ui:CommandBar.PrimaryCommands>
</ui:CommandBar>
</Border>
</Design.PreviewWith>
<!-- Success -->
<Style Selector="ui|CommandBarButton.success">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeGreenColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeGreenColor}" />
</Style>
<Style Selector="^ /template/ TextBlock#TextLabel">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeDarkGreenColor}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkGreenColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="Green" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeDarkDarkGreenColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkDarkGreenColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- Danger -->
<Style Selector="ui|CommandBarButton.danger">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeRedColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeRedColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeDarkRedColor}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkRedColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeDarkDarkRedColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkDarkRedColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- Info -->
<Style Selector="ui|CommandBarButton.info">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeLightBlueColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeLightBlueColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeBlueColor}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBlueColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkBlueColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!--Accent Button-->
<Style Selector="ui|CommandBarButton.accent">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- SystemAccent -->
<Style Selector="ui|CommandBarButton.systemaccent">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark1}"/>
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorDark1}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark2}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorDark2}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- Transparent -->
<Style Selector="ui|CommandBarButton.transparent">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundPressed}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- Semi-Transparent Info -->
<Style Selector="ui|CommandBarButton.transparent-info">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeLightBlueColorTransparent}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeLightBlueColorTransparent}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeBlueColorTransparent}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBlueColorTransparent}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeDarkBlueColorTransparent}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkBlueColorTransparent}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- Transparent red -->
<Style Selector="ui|CommandBarButton.transparent-red">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeCoralRedColor}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeCoralRedColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ThemeDarkCoralRedColor}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkCoralRedColor}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
<!-- Full Transparent -->
<Style Selector="ui|CommandBarButton.transparent-full">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlTransparentBrush}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundDisabled}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundPressed}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder">
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlTransparentBrush}" />
</Style>
<Style Selector="^">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
</Styles>

1
StabilityMatrix.Avalonia/Styles/ThemeColors.axaml

@ -21,6 +21,7 @@
<Color x:Key="ThemeCyanColor">#00BCD4</Color>
<Color x:Key="ThemeTealColor">#009688</Color>
<Color x:Key="ThemeDarkDarkGreenColor">#2C582C</Color>
<SolidColorBrush x:Key="ThemeDarkDarkGreenColorBrush">#2C582C</SolidColorBrush>
<Color x:Key="ThemeDarkGreenColor">#3A783C</Color>
<Color x:Key="ThemeGreenColor">#4BA04F</Color>
<Color x:Key="ThemeGreenColorTransparent">#AA4BA04F</Color>

12
StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs

@ -59,6 +59,7 @@ public abstract partial class InferenceGenerationViewModelBase
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly ISettingsManager settingsManager;
private readonly RunningPackageService runningPackageService;
private readonly INotificationService notificationService;
private readonly ServiceManager<ViewModelBase> vmFactory;
@ -79,12 +80,14 @@ public abstract partial class InferenceGenerationViewModelBase
ServiceManager<ViewModelBase> vmFactory,
IInferenceClientManager inferenceClientManager,
INotificationService notificationService,
ISettingsManager settingsManager
ISettingsManager settingsManager,
RunningPackageService runningPackageService
)
: base(notificationService)
{
this.notificationService = notificationService;
this.settingsManager = settingsManager;
this.runningPackageService = runningPackageService;
this.vmFactory = vmFactory;
ClientManager = inferenceClientManager;
@ -787,15 +790,12 @@ public abstract partial class InferenceGenerationViewModelBase
return;
// Restart Package
// TODO: This should be handled by some DI package manager service
var launchPage = App.Services.GetRequiredService<LaunchPageViewModel>();
try
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
await launchPage.Stop();
await launchPage.LaunchAsync();
await runningPackageService.StopPackage(localPackagePair.InstalledPackage.Id);
await runningPackageService.StartPackage(localPackagePair.InstalledPackage);
});
}
catch (Exception e)

16
StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs

@ -98,11 +98,7 @@ public abstract partial class InferenceTabViewModelBase
protected async Task<ViewState> SaveViewState()
{
var eventArgs = new SaveViewStateEventArgs();
saveViewStateRequestedEventManager?.RaiseEvent(
this,
eventArgs,
nameof(SaveViewStateRequested)
);
saveViewStateRequestedEventManager?.RaiseEvent(this, eventArgs, nameof(SaveViewStateRequested));
if (eventArgs.StateTask is not { } stateTask)
{
@ -128,7 +124,7 @@ public abstract partial class InferenceTabViewModelBase
// TODO: Dock reset not working, using this hack for now to get a new view
var navService = App.Services.GetRequiredService<INavigationService<MainWindowViewModel>>();
navService.NavigateTo<LaunchPageViewModel>(new SuppressNavigationTransitionInfo());
navService.NavigateTo<NewPackageManagerViewModel>(new SuppressNavigationTransitionInfo());
((IPersistentViewProvider)this).AttachedPersistentView = null;
navService.NavigateTo<InferenceViewModel>(new BetterEntranceNavigationTransition());
}
@ -157,9 +153,7 @@ public abstract partial class InferenceTabViewModelBase
if (result == ContentDialogResult.Primary && textFields[0].Text is { } json)
{
LoadViewState(
new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } }
);
LoadViewState(new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } });
}
}
@ -226,9 +220,7 @@ public abstract partial class InferenceTabViewModelBase
if (this is IParametersLoadableState paramsLoadableVm)
{
Dispatcher.UIThread.Invoke(
() => paramsLoadableVm.LoadStateFromParameters(parameters)
);
Dispatcher.UIThread.Invoke(() => paramsLoadableVm.LoadStateFromParameters(parameters));
}
else
{

3
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs

@ -6,6 +6,7 @@ using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
using StabilityMatrix.Avalonia.Views;
@ -20,7 +21,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[Singleton]
public partial class CheckpointBrowserViewModel : PageViewModelBase
{
public override string Title => "Model Browser";
public override string Title => Resources.Label_ModelBrowser;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.BrainCircuit, IsFilled = true };

12
StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs

@ -111,6 +111,10 @@ public partial class CheckpointFolder : ViewModelBase
public IObservableCollection<string> BaseModelOptions { get; } =
new ObservableCollectionExtended<string>();
private IEnumerable<string> allModelOptions = Enum.GetValues<CivitBaseModelType>()
.Where(x => x != CivitBaseModelType.All)
.Select(x => x.GetStringValue());
public CheckpointFolder(
ISettingsManager settingsManager,
IDownloadService downloadService,
@ -175,11 +179,7 @@ public partial class CheckpointFolder : ViewModelBase
SubFoldersCache.Refresh();
});
BaseModelOptionsCache.AddOrUpdate(
Enum.GetValues<CivitBaseModelType>()
.Where(x => x != CivitBaseModelType.All)
.Select(x => x.GetStringValue())
);
BaseModelOptionsCache.AddOrUpdate(allModelOptions);
CheckpointFiles.CollectionChanged += OnCheckpointFilesChanged;
// DisplayedCheckpointFiles = CheckpointFiles;
@ -187,7 +187,7 @@ public partial class CheckpointFolder : ViewModelBase
private bool BaseModelFilter(CheckpointFile file)
{
return file.IsConnectedModel
return file.IsConnectedModel && allModelOptions.Contains(file.ConnectedModel!.BaseModel)
? BaseModelOptions.Contains(file.ConnectedModel!.BaseModel)
: BaseModelOptions.Contains("Other");
}

3
StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs

@ -25,6 +25,7 @@ using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using Resources = StabilityMatrix.Avalonia.Languages.Resources;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;
using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;
@ -43,7 +44,7 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
private readonly INotificationService notificationService;
private readonly IMetadataImportService metadataImportService;
public override string Title => "Checkpoints";
public override string Title => Resources.Label_Checkpoints;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Notebook, IsFilled = true };

15
StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@ -30,6 +31,7 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
private readonly ISettingsManager settingsManager;
private readonly INavigationService<MainWindowViewModel> navigationService;
private readonly IPackageFactory packageFactory;
private readonly RunningPackageService runningPackageService;
[ObservableProperty]
private string title = "Hello";
@ -58,12 +60,14 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
public InferenceConnectionHelpViewModel(
ISettingsManager settingsManager,
INavigationService<MainWindowViewModel> navigationService,
IPackageFactory packageFactory
IPackageFactory packageFactory,
RunningPackageService runningPackageService
)
{
this.settingsManager = settingsManager;
this.navigationService = navigationService;
this.packageFactory = packageFactory;
this.runningPackageService = runningPackageService;
// Get comfy type installed packages
var comfyPackages = this.settingsManager.Settings.InstalledPackages.Where(
@ -122,14 +126,11 @@ public partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBa
/// Request launch of the selected package
/// </summary>
[RelayCommand]
private void LaunchSelectedPackage()
private async Task LaunchSelectedPackage()
{
if (SelectedPackage?.Id is { } id)
if (SelectedPackage is not null)
{
Dispatcher.UIThread.Post(() =>
{
EventManager.Instance.OnPackageLaunchRequested(id);
});
await runningPackageService.StartPackage(SelectedPackage);
}
}

9
StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Styles;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
@ -27,11 +28,9 @@ public partial class FirstLaunchSetupViewModel : ViewModelBase
private RefreshBadgeViewModel checkHardwareBadge =
new()
{
WorkingToolTipText = "We're checking some hardware specifications to determine compatibility.",
SuccessToolTipText = "Everything looks good!",
FailToolTipText =
"We recommend a GPU with CUDA support for the best experience. "
+ "You can continue without one, but some packages may not work, and inference may be slower.",
WorkingToolTipText = Resources.Label_CheckingHardware,
SuccessToolTipText = Resources.Label_EverythingLooksGood,
FailToolTipText = Resources.Label_NvidiaGpuRecommended,
FailColorBrush = ThemeColors.ThemeYellow,
};

20
StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs

@ -27,9 +27,17 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel
IInferenceClientManager inferenceClientManager,
INotificationService notificationService,
ISettingsManager settingsManager,
IModelIndexService modelIndexService
IModelIndexService modelIndexService,
RunningPackageService runningPackageService
)
: base(notificationService, inferenceClientManager, settingsManager, vmFactory, modelIndexService)
: base(
notificationService,
inferenceClientManager,
settingsManager,
vmFactory,
modelIndexService,
runningPackageService
)
{
SelectImageCardViewModel = vmFactory.Get<SelectImageCardViewModel>();
@ -77,12 +85,12 @@ public class InferenceImageToImageViewModel : InferenceTextToImageViewModel
var mainImages = SelectImageCardViewModel.GetInputImages();
var samplerImages = SamplerCardViewModel
.ModulesCardViewModel
.Cards
.OfType<IInputImageProvider>()
.ModulesCardViewModel.Cards.OfType<IInputImageProvider>()
.SelectMany(m => m.GetInputImages());
var moduleImages = ModulesCardViewModel.Cards.OfType<IInputImageProvider>().SelectMany(m => m.GetInputImages());
var moduleImages = ModulesCardViewModel
.Cards.OfType<IInputImageProvider>()
.SelectMany(m => m.GetInputImages());
return mainImages.Concat(samplerImages).Concat(moduleImages);
}

5
StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs

@ -65,9 +65,10 @@ public partial class InferenceImageToVideoViewModel
IInferenceClientManager inferenceClientManager,
ISettingsManager settingsManager,
ServiceManager<ViewModelBase> vmFactory,
IModelIndexService modelIndexService
IModelIndexService modelIndexService,
RunningPackageService runningPackageService
)
: base(vmFactory, inferenceClientManager, notificationService, settingsManager)
: base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)
{
this.notificationService = notificationService;
this.modelIndexService = modelIndexService;

15
StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs

@ -59,9 +59,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase
INotificationService notificationService,
IInferenceClientManager inferenceClientManager,
ISettingsManager settingsManager,
ServiceManager<ViewModelBase> vmFactory
ServiceManager<ViewModelBase> vmFactory,
RunningPackageService runningPackageService
)
: base(vmFactory, inferenceClientManager, notificationService, settingsManager)
: base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)
{
this.notificationService = notificationService;
@ -142,7 +143,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase
}
/// <inheritdoc />
protected override async Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)
protected override async Task GenerateImageImpl(
GenerateOverrides overrides,
CancellationToken cancellationToken
)
{
if (!ClientManager.IsConnected)
{
@ -169,7 +173,10 @@ public class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase
Client = ClientManager.Client,
Nodes = buildPromptArgs.Builder.ToNodeDictionary(),
OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),
Parameters = new GenerationParameters { ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name, },
Parameters = new GenerationParameters
{
ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name,
},
Project = InferenceProjectDocument.FromLoadable(this)
};

5
StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs

@ -59,9 +59,10 @@ public class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, I
IInferenceClientManager inferenceClientManager,
ISettingsManager settingsManager,
ServiceManager<ViewModelBase> vmFactory,
IModelIndexService modelIndexService
IModelIndexService modelIndexService,
RunningPackageService runningPackageService
)
: base(vmFactory, inferenceClientManager, notificationService, settingsManager)
: base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)
{
this.notificationService = notificationService;
this.modelIndexService = modelIndexService;

13
StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs

@ -90,9 +90,18 @@ public partial class SelectImageCardViewModel(INotificationService notificationS
partial void OnImageSourceChanged(ImageSource? value)
{
// Cache the hash for later upload use
if (value?.LocalFile is { Exists: true })
if (value?.LocalFile is { Exists: true } localFile)
{
value.GetBlake3HashAsync().SafeFireAndForget();
value
.GetBlake3HashAsync()
.SafeFireAndForget(ex =>
{
Logger.Warn(ex, "Error getting hash for image {Path}", localFile.Name);
notificationService.ShowPersistent(
$"Error getting hash for image {localFile.Name}",
$"{ex.GetType().Name}: {ex.Message}"
);
});
}
}

117
StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Text.Json;
@ -16,6 +18,7 @@ using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using NLog;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@ -51,8 +54,10 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
private readonly ServiceManager<ViewModelBase> vmFactory;
private readonly IModelIndexService modelIndexService;
private readonly ILiteDbContext liteDbContext;
private readonly RunningPackageService runningPackageService;
private Guid? selectedPackageId;
public override string Title => "Inference";
public override string Title => Resources.Label_Inference;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.AppGeneric, IsFilled = true };
@ -62,7 +67,7 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
State = ProgressState.Failed,
FailToolTipText = "Not connected",
FailIcon = FluentAvalonia.UI.Controls.Symbol.Refresh,
SuccessToolTipText = "Connected",
SuccessToolTipText = Resources.Label_Connected,
};
public IInferenceClientManager ClientManager { get; }
@ -86,6 +91,8 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
public bool IsComfyRunning => RunningPackage?.BasePackage is ComfyUI;
private IDisposable? onStartupComplete;
public InferenceViewModel(
ServiceManager<ViewModelBase> vmFactory,
INotificationService notificationService,
@ -93,6 +100,7 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
ISettingsManager settingsManager,
IModelIndexService modelIndexService,
ILiteDbContext liteDbContext,
RunningPackageService runningPackageService,
SharedState sharedState
)
{
@ -101,12 +109,13 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
this.settingsManager = settingsManager;
this.modelIndexService = modelIndexService;
this.liteDbContext = liteDbContext;
this.runningPackageService = runningPackageService;
ClientManager = inferenceClientManager;
SharedState = sharedState;
// Keep RunningPackage updated with the current package pair
EventManager.Instance.RunningPackageStatusChanged += OnRunningPackageStatusChanged;
runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged;
// "Send to Inference"
EventManager.Instance.InferenceTextToImageRequested += OnInferenceTextToImageRequested;
@ -121,54 +130,77 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
MenuOpenProjectCommand.WithConditionalNotificationErrorHandler(notificationService);
}
private void DisconnectFromComfy()
{
RunningPackage = null;
// Cancel any pending connection
if (ConnectCancelCommand.CanExecute(null))
{
ConnectCancelCommand.Execute(null);
}
onStartupComplete?.Dispose();
onStartupComplete = null;
IsWaitingForConnection = false;
// Disconnect
Logger.Trace("On package close - disconnecting");
DisconnectCommand.Execute(null);
}
/// <summary>
/// Updates the RunningPackage property when the running package changes.
/// Also starts a connection to the backend if a new ComfyUI package is running.
/// And disconnects if the package is closed.
/// </summary>
private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusChangedEventArgs e)
private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
RunningPackage = e.CurrentPackagePair;
if (
e.NewItems?.OfType<KeyValuePair<Guid, RunningPackageViewModel>>().Select(x => x.Value)
is not { } newItems
)
{
if (RunningPackage != null)
{
DisconnectFromComfy();
}
return;
}
IDisposable? onStartupComplete = null;
var comfyViewModel = newItems.FirstOrDefault(
vm =>
vm.RunningPackage.InstalledPackage.Id == selectedPackageId
|| vm.RunningPackage.BasePackage is ComfyUI
);
Dispatcher.UIThread.Post(() =>
if (comfyViewModel is null && RunningPackage?.BasePackage is ComfyUI)
{
if (e.CurrentPackagePair?.BasePackage is ComfyUI package)
{
IsWaitingForConnection = true;
onStartupComplete = Observable
.FromEventPattern<string>(package, nameof(package.StartupComplete))
.Take(1)
.Subscribe(_ =>
DisconnectFromComfy();
}
else if (comfyViewModel != null && RunningPackage == null)
{
IsWaitingForConnection = true;
RunningPackage = comfyViewModel.RunningPackage;
onStartupComplete = Observable
.FromEventPattern<string>(
comfyViewModel.RunningPackage.BasePackage,
nameof(comfyViewModel.RunningPackage.BasePackage.StartupComplete)
)
.Take(1)
.Subscribe(_ =>
{
Dispatcher.UIThread.Post(() =>
{
Dispatcher.UIThread.Post(() =>
if (ConnectCommand.CanExecute(null))
{
if (ConnectCommand.CanExecute(null))
{
Logger.Trace("On package launch - starting connection");
ConnectCommand.Execute(null);
}
IsWaitingForConnection = false;
});
});
}
else
{
// Cancel any pending connection
if (ConnectCancelCommand.CanExecute(null))
{
ConnectCancelCommand.Execute(null);
}
onStartupComplete?.Dispose();
onStartupComplete = null;
IsWaitingForConnection = false;
Logger.Trace("On package launch - starting connection");
ConnectCommand.Execute(null);
}
// Disconnect
Logger.Trace("On package close - disconnecting");
DisconnectCommand.Execute(null);
}
});
IsWaitingForConnection = false;
});
});
}
}
private void OnInferenceQueueCustomPromptRequested(object? sender, InferenceQueueCustomPromptEventArgs e)
@ -421,7 +453,12 @@ public partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable
private async Task ShowConnectionHelp()
{
var vm = vmFactory.Get<InferenceConnectionHelpViewModel>();
await vm.CreateDialog().ShowAsync();
var result = await vm.CreateDialog().ShowAsync();
if (result != ContentDialogResult.Primary)
return;
selectedPackageId = vm.SelectedPackage?.Id;
}
/// <summary>

8
StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs

@ -139,14 +139,6 @@ public partial class MainWindowViewModel : ViewModelBase
Content = new NewOneClickInstallDialog { DataContext = viewModel },
};
EventManager.Instance.OneClickInstallFinished += (_, skipped) =>
{
if (skipped)
return;
EventManager.Instance.OnTeachingTooltipNeeded();
};
var firstDialogResult = await dialog.ShowAsync(App.TopLevel);
if (firstDialogResult != ContentDialogResult.Primary)

3
StabilityMatrix.Avalonia/ViewModels/NewCheckpointsPageViewModel.cs

@ -14,6 +14,7 @@ using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using Refit;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
@ -47,7 +48,7 @@ public partial class NewCheckpointsPageViewModel(
IMetadataImportService metadataImportService
) : PageViewModelBase
{
public override string Title => "Checkpoint Manager";
public override string Title => Resources.Label_CheckpointManager;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Cellular5g, IsFilled = true };

7
StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs

@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
@ -18,7 +19,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[Singleton]
public partial class NewPackageManagerViewModel : PageViewModelBase
{
public override string Title => "Packages";
public override string Title => Resources.Label_Packages;
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
public IReadOnlyList<PageViewModelBase> SubPages { get; }
@ -58,6 +59,10 @@ public partial class NewPackageManagerViewModel : PageViewModelBase
{
CurrentPagePath.Add(value);
}
else if (value is RunningPackageViewModel)
{
CurrentPagePath.Add(value);
}
else
{
CurrentPagePath.Clear();

55
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -86,6 +86,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty]
private bool isConsolidating;
[ObservableProperty]
private bool showFolders;
public bool CanShowOutputTypes => SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
public string NumImagesSelected =>
@ -136,6 +139,13 @@ public partial class OutputsPageViewModel : PageViewModelBase
settings => settings.OutputsImageSize,
delay: TimeSpan.FromMilliseconds(250)
);
settingsManager.RelayPropertyFor(
this,
vm => vm.ShowFolders,
settings => settings.IsOutputsTreeViewEnabled,
true
);
}
protected override void OnInitialLoaded()
@ -273,8 +283,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
var confirmationDialog = new BetterContentDialog
{
Title = "Are you sure you want to delete this image?",
Content = "This action cannot be undone.",
Title = Resources.Label_AreYouSure,
Content = Resources.Label_ActionCannotBeUndone,
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
@ -352,8 +362,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
{
var confirmationDialog = new BetterContentDialog
{
Title = $"Are you sure you want to delete {NumItemsSelected} images?",
Content = "This action cannot be undone.",
Title = string.Format(Resources.Label_AreYouSureDeleteImages, NumItemsSelected),
Content = Resources.Label_ActionCannotBeUndone,
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
@ -559,7 +569,10 @@ public partial class OutputsPageViewModel : PageViewModelBase
pair.InstalledPackage.FullPath!,
pair.BasePackage.OutputFolderName
),
Name = pair.InstalledPackage.DisplayName ?? ""
Name = pair.InstalledPackage.DisplayName ?? "",
SubDirectories = GetSubfolders(
Path.Combine(pair.InstalledPackage.FullPath!, pair.BasePackage.OutputFolderName)
)
}
)
.ToList();
@ -569,18 +582,38 @@ public partial class OutputsPageViewModel : PageViewModelBase
new PackageOutputCategory
{
Path = settingsManager.ImagesDirectory,
Name = "Shared Output Folder"
Name = "Shared Output Folder",
SubDirectories = GetSubfolders(settingsManager.ImagesDirectory)
}
);
packageCategories.Insert(
1,
new PackageOutputCategory { Path = settingsManager.ImagesInferenceDirectory, Name = "Inference" }
);
Categories = new ObservableCollection<PackageOutputCategory>(packageCategories);
SelectedCategory =
Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name) ?? Categories.First();
}
private ObservableCollection<PackageOutputCategory> GetSubfolders(string strPath)
{
var subfolders = new ObservableCollection<PackageOutputCategory>();
if (!Directory.Exists(strPath))
return subfolders;
var directories = Directory.EnumerateDirectories(strPath, "*", SearchOption.TopDirectoryOnly);
foreach (var dir in directories)
{
var category = new PackageOutputCategory { Name = Path.GetFileName(dir), Path = dir };
if (Directory.GetDirectories(dir, "*", SearchOption.TopDirectoryOnly).Length > 0)
{
category.SubDirectories = GetSubfolders(dir);
}
subfolders.Add(category);
}
return subfolders;
}
}

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

@ -1,11 +1,16 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
@ -26,7 +31,6 @@ using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Settings;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
@ -34,14 +38,17 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
[ManagedService]
[Transient]
public partial class PackageCardViewModel : ProgressViewModel
public partial class PackageCardViewModel(
ILogger<PackageCardViewModel> logger,
IPackageFactory packageFactory,
INotificationService notificationService,
ISettingsManager settingsManager,
INavigationService<NewPackageManagerViewModel> navigationService,
ServiceManager<ViewModelBase> vmFactory,
RunningPackageService runningPackageService
) : ProgressViewModel
{
private readonly ILogger<PackageCardViewModel> logger;
private readonly IPackageFactory packageFactory;
private readonly INotificationService notificationService;
private readonly ISettingsManager settingsManager;
private readonly INavigationService<MainWindowViewModel> navigationService;
private readonly ServiceManager<ViewModelBase> vmFactory;
private string webUiUrl = string.Empty;
[ObservableProperty]
private InstalledPackage? package;
@ -82,21 +89,31 @@ public partial class PackageCardViewModel : ProgressViewModel
[ObservableProperty]
private bool canUseExtensions;
public PackageCardViewModel(
ILogger<PackageCardViewModel> logger,
IPackageFactory packageFactory,
INotificationService notificationService,
ISettingsManager settingsManager,
INavigationService<MainWindowViewModel> navigationService,
ServiceManager<ViewModelBase> vmFactory
)
[ObservableProperty]
private bool isRunning;
[ObservableProperty]
private bool showWebUiButton;
private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
this.logger = logger;
this.packageFactory = packageFactory;
this.notificationService = notificationService;
this.settingsManager = settingsManager;
this.navigationService = navigationService;
this.vmFactory = vmFactory;
if (runningPackageService.RunningPackages.Select(x => x.Value) is not { } runningPackages)
return;
var runningViewModel = runningPackages.FirstOrDefault(
x => x.RunningPackage.InstalledPackage.Id == Package?.Id
);
if (runningViewModel is not null)
{
IsRunning = true;
runningViewModel.RunningPackage.BasePackage.Exited += BasePackageOnExited;
runningViewModel.RunningPackage.BasePackage.StartupComplete += RunningPackageOnStartupComplete;
}
else if (runningViewModel is null && IsRunning)
{
IsRunning = false;
ShowWebUiButton = false;
}
}
partial void OnPackageChanged(InstalledPackage? value)
@ -127,11 +144,30 @@ public partial class PackageCardViewModel : ProgressViewModel
UseSharedOutput = Package?.UseSharedOutputFolder ?? false;
CanUseSharedOutput = basePackage?.SharedOutputFolders != null;
CanUseExtensions = basePackage?.SupportsExtensions ?? false;
runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged;
EventManager.Instance.PackageRelaunchRequested += InstanceOnPackageRelaunchRequested;
}
}
private void InstanceOnPackageRelaunchRequested(object? sender, InstalledPackage e)
{
if (e.Id != Package?.Id)
return;
navigationService.GoBack();
Launch().SafeFireAndForget();
}
public override async Task OnLoadedAsync()
{
if (Design.IsDesignMode && Package?.DisplayName == "Running Comfy")
{
IsRunning = true;
IsUpdateAvailable = true;
ShowWebUiButton = true;
}
if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet || Package is not { } currentPackage)
return;
@ -160,18 +196,122 @@ public partial class PackageCardViewModel : ProgressViewModel
}
IsUpdateAvailable = await HasUpdate();
if (
Package != null
&& !IsRunning
&& runningPackageService.RunningPackages.TryGetValue(Package.Id, out var runningPackageVm)
)
{
IsRunning = true;
runningPackageVm.RunningPackage.BasePackage.Exited += BasePackageOnExited;
runningPackageVm.RunningPackage.BasePackage.StartupComplete +=
RunningPackageOnStartupComplete;
webUiUrl = runningPackageVm.WebUiUrl;
ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);
}
}
}
public override void OnUnloaded()
{
EventManager.Instance.PackageRelaunchRequested -= InstanceOnPackageRelaunchRequested;
runningPackageService.RunningPackages.CollectionChanged -= RunningPackagesOnCollectionChanged;
}
public async Task Launch()
{
if (Package == null)
return;
var packagePair = await runningPackageService.StartPackage(Package);
if (packagePair != null)
{
IsRunning = true;
packagePair.BasePackage.Exited += BasePackageOnExited;
packagePair.BasePackage.StartupComplete += RunningPackageOnStartupComplete;
var vm = runningPackageService.GetRunningPackageViewModel(packagePair.InstalledPackage.Id);
if (vm != null)
{
navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
}
}
// settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);
//
// navigationService.NavigateTo<LaunchPageViewModel>(new BetterDrillInNavigationTransition());
// EventManager.Instance.OnPackageLaunchRequested(Package.Id);
}
public void Launch()
public void NavToConsole()
{
if (Package == null)
return;
settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);
var vm = runningPackageService.GetRunningPackageViewModel(Package.Id);
if (vm != null)
{
navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
}
}
public void LaunchWebUi()
{
if (string.IsNullOrEmpty(webUiUrl))
return;
notificationService.TryAsync(
Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)),
"Failed to open URL",
$"{webUiUrl}"
);
}
private void BasePackageOnExited(object? sender, int exitCode)
{
Dispatcher
.UIThread.InvokeAsync(async () =>
{
logger.LogTrace("Process exited ({Code}) at {Time:g}", exitCode, DateTimeOffset.Now);
// Need to wait for streams to finish before detaching handlers
if (sender is BaseGitPackage { VenvRunner: not null } package)
{
var process = package.VenvRunner.Process;
if (process is not null)
{
// Max 5 seconds
var ct = new CancellationTokenSource(5000).Token;
try
{
await process.WaitUntilOutputEOF(ct);
}
catch (OperationCanceledException e)
{
logger.LogWarning("Waiting for process EOF timed out: {Message}", e.Message);
}
}
}
navigationService.NavigateTo<LaunchPageViewModel>(new BetterDrillInNavigationTransition());
EventManager.Instance.OnPackageLaunchRequested(Package.Id);
// Detach handlers
if (sender is BasePackage basePackage)
{
basePackage.Exited -= BasePackageOnExited;
basePackage.StartupComplete -= RunningPackageOnStartupComplete;
}
if (Package?.Id != null)
{
runningPackageService.RunningPackages.Remove(Package.Id);
}
IsRunning = false;
ShowWebUiButton = false;
})
.SafeFireAndForget();
}
public async Task Uninstall()
@ -282,6 +422,21 @@ public partial class PackageCardViewModel : ProgressViewModel
versionOptions.CommitHash = latest.Sha;
}
var confirmationDialog = new BetterContentDialog
{
Title = Resources.Label_AreYouSure,
Content =
$"{Package.DisplayName} will be updated to the latest version ({versionOptions.GetReadableVersionString()})",
PrimaryButtonText = Resources.Action_Continue,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
IsSecondaryButtonEnabled = true,
};
var dialogResult = await confirmationDialog.ShowAsync();
if (dialogResult != ContentDialogResult.Primary)
return;
var updatePackageStep = new UpdatePackageStep(
settingsManager,
Package,
@ -435,6 +590,64 @@ public partial class PackageCardViewModel : ProgressViewModel
ProcessRunner.OpenUrl(basePackage.GithubUrl);
}
[RelayCommand]
private async Task Stop()
{
if (Package is null)
return;
await runningPackageService.StopPackage(Package.Id);
IsRunning = false;
ShowWebUiButton = false;
}
[RelayCommand]
private async Task Restart()
{
await Stop();
await Launch();
}
[RelayCommand]
private async Task ShowLaunchOptions()
{
var basePackage = packageFactory.FindPackageByName(Package?.PackageName);
if (basePackage == null)
{
logger.LogWarning("Package {Name} not found", Package?.PackageName);
return;
}
var viewModel = vmFactory.Get<LaunchOptionsViewModel>();
viewModel.Cards = LaunchOptionCard
.FromDefinitions(basePackage.LaunchOptions, Package?.LaunchArgs ?? [])
.ToImmutableArray();
logger.LogDebug("Launching config dialog with cards: {CardsCount}", viewModel.Cards.Count);
var dialog = new BetterContentDialog
{
ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
IsPrimaryButtonEnabled = true,
PrimaryButtonText = Resources.Action_Save,
CloseButtonText = Resources.Action_Cancel,
FullSizeDesired = true,
DefaultButton = ContentDialogButton.Primary,
ContentMargin = new Thickness(32, 16),
Padding = new Thickness(0, 16),
Content = new LaunchOptionsDialog { DataContext = viewModel, }
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary && Package != null)
{
// Save config
var args = viewModel.AsLaunchArgs();
settingsManager.SaveLaunchArgs(Package.Id, args);
}
}
private async Task<bool> HasUpdate()
{
if (Package == null || IsUnknownPackage || Design.IsDesignMode)
@ -565,4 +778,10 @@ public partial class PackageCardViewModel : ProgressViewModel
IsSharedModelConfig = false;
}
}
private void RunningPackageOnStartupComplete(object? sender, string e)
{
webUiUrl = e.Replace("0.0.0.0", "127.0.0.1");
ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);
}
}

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

@ -281,7 +281,7 @@ public partial class PackageInstallDetailViewModel(
SelectedVersion = !IsReleaseMode
? AvailableVersions?.FirstOrDefault(x => x.TagName == SelectedPackage.MainBranch)
?? AvailableVersions?.FirstOrDefault()
: AvailableVersions?.FirstOrDefault();
: AvailableVersions?.FirstOrDefault(v => !v.IsPrerelease);
CanInstall = !ShowDuplicateWarning;
}

15
StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs

@ -6,8 +6,6 @@ using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
@ -15,18 +13,15 @@ using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.PackageManager;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Services;
using Symbol = FluentIcons.Common.Symbol;
@ -48,7 +43,7 @@ public partial class PackageManagerViewModel : PageViewModelBase
private readonly INavigationService<NewPackageManagerViewModel> packageNavigationService;
private readonly ILogger<PackageManagerViewModel> logger;
public override string Title => "Packages";
public override string Title => Resources.Label_Packages;
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
/// <summary>
@ -84,6 +79,7 @@ public partial class PackageManagerViewModel : PageViewModelBase
this.logger = logger;
EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;
EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;
var installed = installedPackages.Connect();
var unknown = unknownInstalledPackages.Connect();
@ -107,6 +103,11 @@ public partial class PackageManagerViewModel : PageViewModelBase
timer.Tick += async (_, _) => await CheckPackagesForUpdates();
}
private void OnOneClickInstallFinished(object? sender, bool e)
{
OnLoadedAsync().SafeFireAndForget();
}
public void SetPackages(IEnumerable<InstalledPackage> packages)
{
installedPackages.Edit(s => s.Load(packages));

161
StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs

@ -0,0 +1,161 @@
using System;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;
using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(ConsoleOutputPage))]
public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, IAsyncDisposable
{
private readonly ISettingsManager settingsManager;
private readonly INotificationService notificationService;
private readonly RunningPackageService runningPackageService;
public PackagePair RunningPackage { get; }
public ConsoleViewModel Console { get; }
public override string Title => RunningPackage.InstalledPackage.PackageName ?? "Running Package";
public override IconSource IconSource => new SymbolIconSource();
[ObservableProperty]
private bool autoScrollToEnd;
[ObservableProperty]
private bool showWebUiButton;
[ObservableProperty]
private string webUiUrl = string.Empty;
[ObservableProperty]
private bool isRunning = true;
[ObservableProperty]
private string consoleInput = string.Empty;
[ObservableProperty]
private bool showWebUiTeachingTip;
/// <inheritdoc/>
public RunningPackageViewModel(
ISettingsManager settingsManager,
INotificationService notificationService,
RunningPackageService runningPackageService,
PackagePair runningPackage,
ConsoleViewModel console
)
{
this.settingsManager = settingsManager;
this.notificationService = notificationService;
this.runningPackageService = runningPackageService;
RunningPackage = runningPackage;
Console = console;
Console.Document.LineCountChanged += DocumentOnLineCountChanged;
RunningPackage.BasePackage.StartupComplete += BasePackageOnStartupComplete;
RunningPackage.BasePackage.Exited += BasePackageOnExited;
settingsManager.RelayPropertyFor(
this,
vm => vm.AutoScrollToEnd,
settings => settings.AutoScrollLaunchConsoleToEnd,
true
);
}
private void BasePackageOnExited(object? sender, int e)
{
IsRunning = false;
ShowWebUiButton = false;
Console.Document.LineCountChanged -= DocumentOnLineCountChanged;
RunningPackage.BasePackage.StartupComplete -= BasePackageOnStartupComplete;
RunningPackage.BasePackage.Exited -= BasePackageOnExited;
runningPackageService.RunningPackages.Remove(RunningPackage.InstalledPackage.Id);
}
private void BasePackageOnStartupComplete(object? sender, string url)
{
WebUiUrl = url.Replace("0.0.0.0", "127.0.0.1");
ShowWebUiButton = !string.IsNullOrWhiteSpace(WebUiUrl);
if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.WebUiButtonMovedTip))
return;
ShowWebUiTeachingTip = true;
settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.WebUiButtonMovedTip));
}
private void DocumentOnLineCountChanged(object? sender, EventArgs e)
{
if (AutoScrollToEnd)
{
EventManager.Instance.OnScrollToBottomRequested();
}
}
[RelayCommand]
private void LaunchPackage()
{
EventManager.Instance.OnPackageRelaunchRequested(RunningPackage.InstalledPackage);
}
[RelayCommand]
private async Task Stop()
{
await runningPackageService.StopPackage(RunningPackage.InstalledPackage.Id);
Console.PostLine($"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}");
await Console.StopUpdatesAsync();
IsRunning = false;
}
[RelayCommand]
private void LaunchWebUi()
{
if (string.IsNullOrEmpty(WebUiUrl))
return;
notificationService.TryAsync(
Task.Run(() => ProcessRunner.OpenUrl(WebUiUrl)),
"Failed to open URL",
$"{WebUiUrl}"
);
}
[RelayCommand]
private async Task SendToConsole()
{
Console.PostLine(ConsoleInput);
if (RunningPackage?.BasePackage is BaseGitPackage gitPackage)
{
var venv = gitPackage.VenvRunner;
var process = venv?.Process;
if (process is not null)
{
await process.StandardInput.WriteLineAsync(ConsoleInput);
}
}
ConsoleInput = string.Empty;
}
public void Dispose()
{
Console.Dispose();
}
public async ValueTask DisposeAsync()
{
await Console.DisposeAsync();
}
}

93
StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml

@ -0,0 +1,93 @@
<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:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:RunningPackageViewModel"
x:Class="StabilityMatrix.Avalonia.Views.ConsoleOutputPage">
<Grid RowDefinitions="Auto, *, Auto">
<ui:CommandBar Grid.Row="0"
Margin="12,0,0,4"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
DefaultLabelPosition="Right">
<ui:CommandBar.PrimaryCommands>
<ui:CommandBarButton
IconSource="Stop"
IsVisible="{Binding IsRunning}"
Classes="danger"
Command="{Binding StopCommand}"
VerticalAlignment="Center"
Label="{x:Static lang:Resources.Action_Stop}" />
<ui:CommandBarButton
IconSource="Play"
IsVisible="{Binding !IsRunning}"
Classes="success"
Command="{Binding LaunchPackageCommand}"
VerticalAlignment="Center"
Label="{x:Static lang:Resources.Action_Launch}" />
<ui:CommandBarSeparator Margin="6,0,4,0"/>
<ui:CommandBarToggleButton
VerticalAlignment="Center"
IsChecked="{Binding AutoScrollToEnd}"
Label="{x:Static lang:Resources.Label_ToggleAutoScrolling}"
ToolTip.Tip="{x:Static lang:Resources.Label_AutoScrollToEnd}">
<ui:CommandBarToggleButton.IconSource>
<controls:FASymbolIconSource Symbol="fa-solid fa-arrow-down-wide-short" />
</ui:CommandBarToggleButton.IconSource>
</ui:CommandBarToggleButton>
<ui:CommandBarButton
Command="{Binding LaunchWebUiCommand}"
IsVisible="{Binding ShowWebUiButton}"
VerticalAlignment="Center"
x:Name="OpenWebUiButton"
Label="{x:Static lang:Resources.Action_OpenWebUI}">
<ui:CommandBarButton.IconSource>
<controls:FASymbolIconSource Symbol="fa-solid fa-up-right-from-square"/>
</ui:CommandBarButton.IconSource>
</ui:CommandBarButton>
</ui:CommandBar.PrimaryCommands>
</ui:CommandBar>
<avaloniaEdit:TextEditor
Grid.Row="1"
x:Name="Console"
Margin="8,8,16,10"
DataContext="{Binding Console}"
Document="{Binding Document}"
FontFamily="Cascadia Code,Consolas,Menlo,Monospace"
IsReadOnly="True"
LineNumbersForeground="DarkSlateGray"
ShowLineNumbers="True"
VerticalScrollBarVisibility="Auto"
WordWrap="True" />
<Grid Grid.Row="2" ColumnDefinitions="*, Auto"
Margin="16,4,16,16">
<TextBox Grid.Column="0" Text="{Binding ConsoleInput, Mode=TwoWay}"
Margin="0,0,8,0"/>
<Button Grid.Column="1"
Classes="accent"
IsDefault="True"
Content="{x:Static lang:Resources.Action_Send}"
Command="{Binding SendToConsoleCommand}"/>
</Grid>
<ui:TeachingTip Grid.Row="0" Grid.Column="0" Name="TeachingTip1"
Target="{Binding #OpenWebUiButton}"
Title="{x:Static lang:Resources.TeachingTip_WebUiButtonMoved}"
PreferredPlacement="Bottom"
IsOpen="True"/>
</Grid>
</controls:UserControlBase>

54
StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs

@ -0,0 +1,54 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Threading;
using AvaloniaEdit;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Helpers;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
namespace StabilityMatrix.Avalonia.Views;
[Transient]
public partial class ConsoleOutputPage : UserControlBase
{
private const int LineOffset = 5;
public ConsoleOutputPage()
{
InitializeComponent();
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
TextEditorConfigs.Configure(Console, TextEditorPreset.Console);
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
EventManager.Instance.ScrollToBottomRequested -= OnScrollToBottomRequested;
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
EventManager.Instance.ScrollToBottomRequested += OnScrollToBottomRequested;
}
private void OnScrollToBottomRequested(object? sender, EventArgs e)
{
Dispatcher.UIThread.Invoke(() =>
{
var editor = this.FindControl<TextEditor>("Console");
if (editor?.Document == null)
return;
var line = Math.Max(editor.Document.LineCount - LineOffset, 1);
editor.ScrollToLine(line);
});
}
}

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

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using AsyncAwaitBestPractices;
using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls;
@ -193,13 +194,52 @@ public partial class MainWindow : AppWindowBase
protected override void OnClosing(WindowClosingEventArgs e)
{
// Show confirmation if package running
var launchPageViewModel = App.Services.GetRequiredService<LaunchPageViewModel>();
launchPageViewModel.OnMainWindowClosing(e);
var runningPackageService = App.Services.GetRequiredService<RunningPackageService>();
if (
runningPackageService.RunningPackages.Count > 0
&& e.CloseReason is WindowCloseReason.WindowClosing
)
{
e.Cancel = true;
var dialog = CreateExitConfirmDialog();
Dispatcher
.UIThread.InvokeAsync(async () =>
{
if (
(TaskDialogStandardResult)await dialog.ShowAsync(true) == TaskDialogStandardResult.Yes
)
{
App.Services.GetRequiredService<MainWindow>().Hide();
App.Shutdown();
}
})
.SafeFireAndForget();
}
base.OnClosing(e);
}
private static TaskDialog CreateExitConfirmDialog()
{
var dialog = DialogHelper.CreateTaskDialog(
Languages.Resources.Label_ConfirmExit,
Languages.Resources.Label_ConfirmExitDetail
);
dialog.ShowProgressBar = false;
dialog.FooterVisibility = TaskDialogFooterVisibility.Never;
dialog.Buttons = new List<TaskDialogButton>
{
new("Exit", TaskDialogStandardResult.Yes),
TaskDialogButton.CancelButton
};
dialog.Buttons[0].IsDefault = true;
return dialog;
}
/// <inheritdoc />
protected override void OnClosed(EventArgs e)
{

110
StabilityMatrix.Avalonia/Views/OutputsPage.axaml

@ -23,13 +23,29 @@
x:DataType="vm:OutputsPageViewModel"
Focusable="True"
mc:Ignorable="d">
<Grid Margin="16" RowDefinitions="Auto,*">
<Grid
Grid.Row="0"
Margin="0,0,0,16"
ColumnDefinitions="Auto,Auto,*,Auto"
RowDefinitions="Auto,*">
<Grid Margin="16" RowDefinitions="Auto,*"
ColumnDefinitions="Auto, *">
<TreeView Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding ShowFolders}"
Margin="0,0,12,0"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding SubDirectories}">
<TextBlock Text="{Binding Name}" />
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Grid Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Margin="0,0,0,16"
ColumnDefinitions="Auto,Auto,*,Auto"
RowDefinitions="Auto,*">
<Grid.Styles>
<Style Selector="TextBox">
<Setter Property="MinHeight" Value="40" />
@ -39,56 +55,6 @@
</Style>
</Grid.Styles>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="4"
Text="{x:Static lang:Resources.Label_OutputFolder}" />
<ComboBox
Grid.Row="1"
Grid.Column="0"
MinWidth="150"
Margin="4,0"
VerticalAlignment="Center"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}">
<ComboBox.Styles>
<Style Selector="ComboBox /template/ ContentControl#ContentPresenter &gt; StackPanel &gt; TextBlock:nth-child(2)">
<Setter Property="IsVisible" Value="False" />
</Style>
</ComboBox.Styles>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models1:PackageOutputCategory}">
<StackPanel>
<TextBlock Margin="0,4,0,4" Text="{Binding Name, Mode=OneWay}" />
<TextBlock Text="{Binding Path, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="4"
IsVisible="{Binding CanShowOutputTypes}"
Text="{x:Static lang:Resources.Label_OutputType}" />
<ComboBox
Grid.Row="1"
Grid.Column="1"
MinWidth="150"
Margin="4,0"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
IsVisible="{Binding CanShowOutputTypes}"
ItemsSource="{Binding OutputTypes}"
SelectedItem="{Binding SelectedOutputType}" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
Margin="4"
Text="{x:Static lang:Resources.Action_Search}" />
<TextBox
Grid.Row="1"
Grid.Column="2"
@ -104,8 +70,8 @@
<Button Classes="transparent-full"
IsVisible="{Binding SearchQuery.Length}"
Command="{Binding ClearSearchQuery}">
<ui:SymbolIcon Symbol="Cancel" />
</Button>
<ui:SymbolIcon Symbol="Cancel" />
</Button>
<ui:SymbolIcon
IsVisible="{Binding !SearchQuery.Length}"
Margin="0,0,10,0"
@ -129,27 +95,31 @@
VerticalAlignment="Center"
VerticalContentAlignment="Center"
DefaultLabelPosition="Right">
<ui:CommandBar.PrimaryCommands>
<ui:CommandBarButton
<ui:CommandBarButton
IsEnabled="{Binding !!NumItemsSelected}"
Command="{Binding DeleteAllSelected}"
IconSource="Delete" />
<ui:CommandBarSeparator />
<ui:CommandBarButton
<ui:CommandBarButton
Command="{Binding SelectAll}"
IconSource="SelectAll"
IconSource="SelectAll"
Label="{x:Static lang:Resources.Action_SelectAll}" />
<ui:CommandBarButton
<ui:CommandBarButton
Command="{Binding ClearSelection}"
IconSource="ClearSelection"
IconSource="ClearSelection"
IsEnabled="{Binding !!NumItemsSelected}"
Label="{x:Static lang:Resources.Action_ClearSelection}" />
<ui:CommandBarSeparator />
<ui:CommandBarButton
IconSource="Refresh"
<ui:CommandBarButton
IconSource="Refresh"
Command="{Binding Refresh}"
Label="{x:Static lang:Resources.Action_Refresh}" />
<ui:CommandBarToggleButton
IconSource="List"
IsChecked="{Binding ShowFolders}"
Label="Show Folders"/>
</ui:CommandBar.PrimaryCommands>
<ui:CommandBar.SecondaryCommands>
@ -161,7 +131,8 @@
</ui:CommandBar>
</Grid>
<scroll:BetterScrollViewer Grid.Row="1" PointerWheelChanged="ScrollViewer_MouseWheelChanged">
<scroll:BetterScrollViewer Grid.Column="1"
Grid.Row="1" PointerWheelChanged="ScrollViewer_MouseWheelChanged">
<ItemsRepeater
x:Name="ImageRepeater"
VerticalAlignment="Top"
@ -206,7 +177,8 @@
IconSource="Delete"
Text="{x:Static lang:Resources.Action_Delete}" />
<ui:MenuFlyoutSeparator IsVisible="{Binding ImageFile.GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}" />
<ui:MenuFlyoutSeparator
IsVisible="{Binding ImageFile.GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}" />
<ui:MenuFlyoutSubItem
IconSource="Share"

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

@ -40,151 +40,151 @@
CornerRadius="4"
Source="{Binding PreviewImageUri}" />
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock Text="{Binding DisplayName}"
FontWeight="Light"
Margin="16,0,0,0"
FontSize="20" />
<TextBlock Text="{Binding ByAuthor}"
FontWeight="Light"
Margin="16,0,0,0"
FontSize="13" />
<TextBlock Text="{Binding DisplayName}"
FontWeight="Light"
Margin="16,0,0,0"
FontSize="20" />
<TextBlock Text="{Binding ByAuthor}"
FontWeight="Light"
Margin="16,0,0,0"
FontSize="13" />
<TextBlock TextWrapping="Wrap"
Text="{Binding Blurb}"
FontWeight="Light"
Margin="16,8,0,8"
FontSize="16" />
<TextBlock TextWrapping="Wrap"
Text="{Binding Disclaimer}"
IsVisible="{Binding Disclaimer, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Foreground="OrangeRed"
FontWeight="Light"
Margin="16,-4,0,4"
FontSize="14" />
<TextBlock TextWrapping="Wrap"
Text="{Binding Blurb}"
FontWeight="Light"
Margin="16,8,0,8"
FontSize="16" />
<TextBlock TextWrapping="Wrap"
Text="{Binding Disclaimer}"
IsVisible="{Binding Disclaimer, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Foreground="OrangeRed"
FontWeight="Light"
Margin="16,-4,0,4"
FontSize="14" />
<ItemsRepeater Margin="16, 8, 0, 0"
ItemsSource="{Binding AvailableTorchVersions}">
<ItemsRepeater.Layout>
<StackLayout Orientation="Horizontal" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type models:TorchVersion}">
<controls:Card
Tag="{Binding }"
HorizontalAlignment="Left"
Padding="4"
Margin="0,0,8,0"
VerticalAlignment="Top">
<ItemsRepeater Margin="16, 8, 0, 0"
ItemsSource="{Binding AvailableTorchVersions}">
<ItemsRepeater.Layout>
<StackLayout Orientation="Horizontal" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type models:TorchVersion}">
<controls:Card
Tag="{Binding }"
HorizontalAlignment="Left"
Padding="4"
Margin="0,0,8,0"
VerticalAlignment="Top">
<controls:Card.Styles>
<Style Selector="controls|Card[Tag=Cuda]">
<Setter Property="Background"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
<controls:Card.Styles>
<Style Selector="controls|Card[Tag=Cuda]">
<Setter Property="Background"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeGreenColorTransparent}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="NVIDIA"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="NVIDIA"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=Rocm]">
<Setter Property="Background"
Value="{DynamicResource ThemeDarkRedColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeDarkRedColor}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
<Style Selector="controls|Card[Tag=Rocm]">
<Setter Property="Background"
Value="{DynamicResource ThemeDarkRedColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeDarkRedColor}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="AMD (Linux)"
TextAlignment="Center"
VerticalAlignment="Center"
ToolTip.Tip="For AMD GPUs that support ROCm on Linux" />
</Template>
</Setter>
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="AMD (Linux)"
TextAlignment="Center"
VerticalAlignment="Center"
ToolTip.Tip="For AMD GPUs that support ROCm on Linux" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=DirectMl]">
<Setter Property="Background"
Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="DirectML"
TextAlignment="Center"
VerticalAlignment="Center"
ToolTip.Tip="For any DirectX compatible GPU on Windows" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=Mps]">
<Setter Property="Background" Value="White" />
<Setter Property="BorderBrush" Value="White" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
<Style Selector="controls|Card[Tag=DirectMl]">
<Setter Property="Background"
Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="DirectML"
TextAlignment="Center"
VerticalAlignment="Center"
ToolTip.Tip="For any DirectX compatible GPU on Windows" />
</Template>
</Setter>
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="macOS"
TextAlignment="Center"
Foreground="Black"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Style Selector="controls|Card[Tag=Cpu]">
<Setter Property="Background"
Value="{DynamicResource ThemeBlueGreyColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeBlueGreyColor}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
<Style Selector="controls|Card[Tag=Mps]">
<Setter Property="Background" Value="White" />
<Setter Property="BorderBrush" Value="White" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="macOS"
TextAlignment="Center"
Foreground="Black"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="CPU"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
</controls:Card.Styles>
</controls:Card>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<Style Selector="controls|Card[Tag=Cpu]">
<Setter Property="Background"
Value="{DynamicResource ThemeBlueGreyColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeBlueGreyColor}" />
<Style
Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground"
Value="{DynamicResource ButtonForeground}" />
</Style>
<Setter Property="Content">
<Template>
<TextBlock
FontWeight="Medium"
HorizontalAlignment="Center"
Text="CPU"
TextAlignment="Center"
VerticalAlignment="Center" />
</Template>
</Setter>
</Style>
</controls:Card.Styles>
</controls:Card>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</StackPanel>

595
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -10,15 +10,23 @@
xmlns:icons="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:avalonia="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:markupExtensions="clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="450"
x:DataType="viewModels:PackageManagerViewModel"
x:CompileBindings="True"
d:DataContext="{x:Static designData:DesignData.PackageManagerViewModel}"
x:Class="StabilityMatrix.Avalonia.Views.PackageManagerPage">
<controls:UserControlBase.Resources>
<converters:BooleanChoiceMultiConverter x:Key="BoolChoiceMultiConverter" />
</controls:UserControlBase.Resources>
<Grid Margin="16" RowDefinitions="Auto,*,Auto">
<ScrollViewer Grid.Row="1">
<ItemsRepeater
x:Name="PackageCardsRepeater"
ItemsSource="{Binding PackageCards}">
<ItemsRepeater.Layout>
<UniformGridLayout MinColumnSpacing="12" MinRowSpacing="12" />
@ -26,185 +34,34 @@
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type packageManager:PackageCardViewModel}">
<controls:Card Padding="8"
MinWidth="400"
CornerRadius="8">
<Grid RowDefinitions="Auto, Auto, Auto, Auto"
ColumnDefinitions="*,Auto">
<TextBlock Grid.Row="0"
Grid.Column="0"
ToolTip.Tip="{Binding Package.DisplayName, FallbackValue=''}"
Text="{Binding Package.DisplayName, FallbackValue=''}"
TextTrimming="WordEllipsis"
TextAlignment="Left" />
<Button
Grid.Row="0"
Grid.Column="1"
HorizontalContentAlignment="Right"
HorizontalAlignment="Right"
VerticalContentAlignment="Top"
VerticalAlignment="Top"
Padding="4,1"
Margin="4,0,0,0"
Classes="transparent"
Width="24"
BorderThickness="0">
<ui:SymbolIcon FontSize="18" Symbol="MoreVertical" />
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft">
<MenuItem Header="{x:Static lang:Resources.Action_CheckForUpdates}"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding OnLoadedAsync}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Refresh" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
Header="{OnPlatform Default={x:Static lang:Resources.Action_OpenInExplorer}, macOS={x:Static lang:Resources.Action_OpenInFinder}}"
Command="{Binding OpenFolder}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="OpenFolder" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
Header="{x:Static lang:Resources.Action_OpenGithub}"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding OpenOnGitHubCommand}">
<MenuItem.Icon>
<icons:Icon
Value="fa-brands fa-github" />
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem
Header="{x:Static lang:Resources.Label_PythonPackages}"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding OpenPythonPackagesDialogCommand}">
<MenuItem.Icon>
<icons:Icon
Value="fa-brands fa-python" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
Header="Extensions"
IsVisible="{Binding CanUseExtensions}"
Command="{Binding OpenExtensionsDialogCommand}">
<MenuItem.Icon>
<icons:Icon
Value="fa-solid fa-puzzle-piece" />
</MenuItem.Icon>
</MenuItem>
<Separator IsVisible="{Binding CanUseSharedOutput}" />
<MenuItem Header="{x:Static lang:Resources.Label_SharedModelStrategyShort}"
IsVisible="{Binding !IsUnknownPackage}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="FolderLink" />
</MenuItem.Icon>
<!-- ReSharper disable Xaml.RedundantResource -->
<MenuItem Header="Symlink"
Command="{Binding ToggleSharedModelSymlink}"
IsVisible="{Binding CanUseSymlinkMethod}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsSharedModelSymlink}"
Margin="8,0,0,0"
Padding="0"
Width="28" Height="28">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Config"
Command="{Binding ToggleSharedModelConfig}"
IsVisible="{Binding CanUseConfigMethod}">
<MenuItem.Icon>
<CheckBox Margin="8,0,0,0"
Padding="0"
Width="28" Height="28"
IsChecked="{Binding IsSharedModelConfig}">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="None"
Command="{Binding ToggleSharedModelNone}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsSharedModelDisabled}"
Margin="8,0,0,0"
Padding="0"
Width="28" Height="28">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<!-- ReSharper enable Xaml.RedundantResource -->
</MenuItem>
<MenuItem
Header="{x:Static lang:Resources.Label_UseSharedOutputFolder}"
Command="{Binding ToggleSharedOutput}"
IsVisible="{Binding CanUseSharedOutput}">
<MenuItem.Icon>
<CheckBox Margin="8,0,0,0"
Padding="0"
Width="28" Height="28"
IsChecked="{Binding UseSharedOutput}">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="{x:Static lang:Resources.Action_Uninstall}"
Command="{Binding Uninstall}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Delete" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Button.Flyout>
</Button>
<TextBlock Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="2,0,0,0"
VerticalAlignment="Center"
Text="{Binding InstalledVersion}" />
<!-- Normal packages shows image -->
<controls:Card.Background>
<MultiBinding Converter="{StaticResource BoolChoiceMultiConverter}">
<Binding Path="IsRunning" />
<DynamicResource ResourceKey="ThemeDarkDarkGreenColorBrush" />
<DynamicResource ResourceKey="ButtonBackground" />
</MultiBinding>
</controls:Card.Background>
<Grid ColumnDefinitions="Auto, *">
<controls:BetterAdvancedImage
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,8,0,8"
Height="180"
Width="225"
CornerRadius="4"
Height="150"
Width="150"
Margin="4, 8"
CornerRadius="8"
HorizontalAlignment="Center"
VerticalContentAlignment="Top"
HorizontalContentAlignment="Center"
Source="{Binding CardImageSource}"
IsVisible="{Binding !IsUnknownPackage}"
Stretch="UniformToFill" />
<!-- Unknown packages panel -->
<Border
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,8,0,8"
Height="180"
Width="225"
CornerRadius="4"
Height="150"
Width="150"
Margin="4, 8"
CornerRadius="8"
HorizontalAlignment="Center"
IsVisible="{Binding IsUnknownPackage}"
Background="#202020">
@ -215,99 +72,340 @@
TextWrapping="Wrap"
Text="{x:Static lang:Resources.Label_UnknownPackage}" />
</Border>
<Grid Grid.Column="1" RowDefinitions="Auto, Auto, Auto, *"
ColumnDefinitions="Auto, *">
<TextBlock Grid.Column="0"
Grid.Row="0"
Text="{Binding Package.DisplayName}"
Margin="8,0,8,0"
FontWeight="SemiBold"
MaxWidth="200"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
ToolTip.Tip="{Binding Package.DisplayName}"
FontSize="16" />
<TextBlock Grid.Column="0"
Grid.Row="1"
FontSize="13"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Margin="8,0,8,0"
VerticalAlignment="Center"
Text="{Binding InstalledVersion}" />
<Button
Grid.Row="0"
Grid.Column="1"
HorizontalContentAlignment="Right"
HorizontalAlignment="Right"
VerticalContentAlignment="Top"
VerticalAlignment="Top"
Padding="4"
Margin="4,0,0,0"
Classes="transparent"
Width="24"
BorderThickness="0">
<ui:SymbolIcon FontSize="18" Symbol="MoreVertical" />
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft">
<MenuItem Header="{x:Static lang:Resources.Action_CheckForUpdates}"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding OnLoadedAsync}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Refresh" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
Header="{OnPlatform Default={x:Static lang:Resources.Action_OpenInExplorer}, macOS={x:Static lang:Resources.Action_OpenInFinder}}"
Command="{Binding OpenFolder}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="OpenFolder" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
Header="{x:Static lang:Resources.Action_OpenGithub}"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding OpenOnGitHubCommand}">
<MenuItem.Icon>
<icons:Icon
Value="fa-brands fa-github" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<Grid
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
IsVisible="{Binding IsUpdateAvailable}"
ColumnDefinitions="*, *">
<!-- Launch and update buttons -->
<Button Grid.Column="0" Classes="accent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Command="{Binding Launch}">
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-rocket"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Launch}" />
</StackPanel>
</Button>
<Button Grid.Column="1" Classes="accent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Command="{Binding Update}">
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-download"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Update}" />
</StackPanel>
</Button>
</Grid>
<MenuItem
Header="{x:Static lang:Resources.Label_PythonPackages}"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding OpenPythonPackagesDialogCommand}">
<MenuItem.Icon>
<icons:Icon
Value="fa-brands fa-python" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
Header="Extensions"
IsVisible="{Binding CanUseExtensions}"
Command="{Binding OpenExtensionsDialogCommand}">
<MenuItem.Icon>
<icons:Icon
Value="fa-solid fa-puzzle-piece" />
</MenuItem.Icon>
</MenuItem>
<!-- Big launch button -->
<Button
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
Classes="accent"
VerticalAlignment="Bottom"
Command="{Binding Launch}"
HorizontalAlignment="Stretch">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsUpdateAvailable" />
<Binding Path="!IsUnknownPackage" />
</MultiBinding>
</Button.IsVisible>
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-rocket"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Launch}" />
</StackPanel>
</Button>
<Separator IsVisible="{Binding CanUseSharedOutput}" />
<!-- Import button (for unknown) -->
<Button Grid.Row="3" Grid.Column="0" Classes="transparent-info"
Grid.ColumnSpan="2"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Command="{Binding Import}"
IsVisible="{Binding IsUnknownPackage}">
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-circle-question"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Import}" />
<MenuItem
Header="{x:Static lang:Resources.Label_SharedModelStrategyShort}"
IsVisible="{Binding !IsUnknownPackage}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="FolderLink" />
</MenuItem.Icon>
<!-- ReSharper disable Xaml.RedundantResource -->
<MenuItem Header="Symlink"
Command="{Binding ToggleSharedModelSymlink}"
IsVisible="{Binding CanUseSymlinkMethod}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsSharedModelSymlink}"
Margin="8,0,0,0"
Padding="0"
Width="28" Height="28">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5" />
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Config"
Command="{Binding ToggleSharedModelConfig}"
IsVisible="{Binding CanUseConfigMethod}">
<MenuItem.Icon>
<CheckBox Margin="8,0,0,0"
Padding="0"
Width="28" Height="28"
IsChecked="{Binding IsSharedModelConfig}">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5" />
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="None"
Command="{Binding ToggleSharedModelNone}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsSharedModelDisabled}"
Margin="8,0,0,0"
Padding="0"
Width="28" Height="28">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5" />
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<!-- ReSharper enable Xaml.RedundantResource -->
</MenuItem>
<MenuItem
Header="{x:Static lang:Resources.Label_UseSharedOutputFolder}"
Command="{Binding ToggleSharedOutput}"
IsVisible="{Binding CanUseSharedOutput}">
<MenuItem.Icon>
<CheckBox Margin="8,0,0,0"
Padding="0"
Width="28" Height="28"
IsChecked="{Binding UseSharedOutput}">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5" />
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="{x:Static lang:Resources.Action_Uninstall}"
Command="{Binding Uninstall}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Delete" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Button.Flyout>
</Button>
<StackPanel Grid.Row="2"
Grid.Column="0"
Margin="0,4,0,0"
Orientation="Horizontal">
<Button Classes="transparent-full"
Margin="4,4,0,0"
Padding="4"
ToolTip.Tip="{x:Static lang:Resources.Action_CheckForUpdates}"
Command="{Binding OnLoadedAsync}"
>
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsUnknownPackage" />
<Binding Path="!IsUpdateAvailable" />
</MultiBinding>
</Button.IsVisible>
<ui:SymbolIcon Symbol="Refresh"
FontSize="20"/>
</Button>
<Button Classes="transparent-full"
Margin="4,4,0,0"
Padding="4"
IsEnabled="{Binding !IsRunning}"
ToolTip.Tip="{x:Static lang:Resources.Label_UpdateAvailable}"
markupExtensions:ShowDisabledTooltipExtension.ShowOnDisabled="True"
Command="{Binding Update}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsUnknownPackage" />
<Binding Path="IsUpdateAvailable" />
</MultiBinding>
</Button.IsVisible>
<ui:SymbolIcon Symbol="CloudDownload"
IsEnabled="{Binding !IsRunning}"
FontSize="24">
<ui:SymbolIcon.Styles>
<Style Selector="ui|SymbolIcon">
<Setter Property="Foreground" Value="Lime" />
</Style>
<Style Selector="ui|SymbolIcon:disabled">
<Setter Property="Foreground" Value="Gray" />
</Style>
</ui:SymbolIcon.Styles>
</ui:SymbolIcon>
</Button>
<Button Classes="transparent-full"
Margin="4,4,0,0"
Padding="4"
ToolTip.Tip="Launch Options"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding ShowLaunchOptionsCommand}">
<ui:SymbolIcon Symbol="SettingsFilled"
FontSize="20"/>
</Button>
<Button Classes="transparent-full"
Margin="6,2,0,0"
Padding="4"
ToolTip.Tip="Extensions"
IsVisible="{Binding CanUseExtensions}"
Command="{Binding OpenExtensionsDialogCommand}">
<icons:Icon FontSize="18"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Value="fa-solid fa-puzzle-piece" />
</Button>
</StackPanel>
</Button>
<UniformGrid Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="3"
VerticalAlignment="Bottom"
Margin="8,8,8,0">
<Button Classes="accent"
Name="LaunchButton"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Command="{Binding Launch}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsRunning" />
<Binding Path="!IsUnknownPackage" />
</MultiBinding>
</Button.IsVisible>
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-rocket"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Launch}" />
</StackPanel>
</Button>
<Button Classes="borderless-danger"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
IsVisible="{Binding IsRunning}"
Command="{Binding StopCommand}">
<StackPanel Orientation="Horizontal"
Margin="0,2">
<icons:Icon Value="fa-solid fa-stop"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Stop}" />
</StackPanel>
</Button>
<Button Classes="borderless-info"
BorderBrush="Transparent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
IsVisible="{Binding IsRunning}"
Command="{Binding RestartCommand}">
<StackPanel Orientation="Horizontal"
Margin="0,2">
<icons:Icon Value="fa-solid fa-arrow-rotate-left"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Restart}" />
</StackPanel>
</Button>
<Button Classes="accent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
IsVisible="{Binding IsRunning}"
Command="{Binding NavToConsole}">
<StackPanel Orientation="Horizontal"
Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-terminal"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Label_Console}" />
</StackPanel>
</Button>
<Button Classes="accent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
IsVisible="{Binding ShowWebUiButton}"
Command="{Binding LaunchWebUi}">
<StackPanel Orientation="Horizontal" Margin="8,2,8,2">
<icons:Icon Value="fa-solid fa-up-right-from-square"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Label_WebUi}" />
</StackPanel>
</Button>
<Button Classes="transparent-info"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Command="{Binding Import}"
IsVisible="{Binding IsUnknownPackage}">
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-circle-question"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Import}" />
</StackPanel>
</Button>
</UniformGrid>
</Grid>
<!-- Update overlay -->
<Border
Grid.Row="0" Grid.RowSpan="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="#DD000000"
CornerRadius="4"
CornerRadius="8"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding IsProgressVisible}" />
<Grid Grid.Row="0" Grid.RowSpan="4"
Grid.Column="0"
IsVisible="{Binding IsProgressVisible}"/>
<Grid Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RowDefinitions="Auto, *"
IsVisible="{Binding IsProgressVisible}">
IsVisible="{Binding IsProgressVisible}"
RowDefinitions="Auto, *">
<controls:ProgressRing
HorizontalAlignment="Center"
IsIndeterminate="{Binding IsIndeterminate}"
Width="150"
Height="150"
Width="120"
Height="120"
StartAngle="90"
EndAngle="450"
Value="{Binding Value}"
@ -334,6 +432,15 @@
Title="{x:Static lang:Resources.TeachingTip_AddPackageToGetStarted}"
PreferredPlacement="Top"
IsOpen="{Binding !Packages.Count}" />
<ui:TeachingTip Grid.Row="0"
Name="LaunchTeachingTip"
MinWidth="100"
Title="{x:Static lang:Resources.TeachingTip_ClickLaunchToGetStarted}"
PreferredPlacement="Bottom"
Margin="8,0,0,0"
PlacementMargin="0,0,0,0"
TailVisibility="Auto"/>
<!-- Add Packages Button -->
<Button Grid.Row="2"

33
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml.cs

@ -1,12 +1,18 @@
using Avalonia.Interactivity;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Packages;
namespace StabilityMatrix.Avalonia.Views;
@ -19,6 +25,31 @@ public partial class PackageManagerPage : UserControlBase
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, OnNavigatedTo, RoutingStrategies.Direct);
EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;
}
private void OnOneClickInstallFinished(object? sender, bool skipped)
{
if (skipped)
return;
Dispatcher.UIThread.Invoke(() =>
{
var target = this.FindDescendantOfType<UniformGrid>()
?.GetVisualChildren()
.OfType<Button>()
.FirstOrDefault(x => x is { Name: "LaunchButton" });
if (target == null)
return;
var teachingTip = this.FindControl<TeachingTip>("LaunchTeachingTip");
if (teachingTip == null)
return;
teachingTip.Target = target;
teachingTip.IsOpen = true;
});
}
private void InitializeComponent()

4
StabilityMatrix.Core/Helper/EventManager.cs

@ -23,6 +23,7 @@ public class EventManager
public event EventHandler<bool>? DevModeSettingChanged;
public event EventHandler<UpdateInfo>? UpdateAvailable;
public event EventHandler<Guid>? PackageLaunchRequested;
public event EventHandler<InstalledPackage>? PackageRelaunchRequested;
public event EventHandler? ScrollToBottomRequested;
public event EventHandler<ProgressItem>? ProgressChanged;
public event EventHandler<RunningPackageStatusChangedEventArgs>? RunningPackageStatusChanged;
@ -104,4 +105,7 @@ public class EventManager
public void OnRecommendedModelsDialogClosed() =>
RecommendedModelsDialogClosed?.Invoke(this, EventArgs.Empty);
public void OnPackageRelaunchRequested(InstalledPackage package) =>
PackageRelaunchRequested?.Invoke(this, package);
}

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

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

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

@ -1,22 +1,94 @@
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Helper.Factory;
[Singleton(typeof(IPackageFactory))]
public class PackageFactory : IPackageFactory
{
private readonly IGithubApiCache githubApiCache;
private readonly ISettingsManager settingsManager;
private readonly IDownloadService downloadService;
private readonly IPrerequisiteHelper prerequisiteHelper;
private readonly IPyRunner pyRunner;
/// <summary>
/// Mapping of package.Name to package
/// </summary>
private readonly Dictionary<string, BasePackage> basePackages;
public PackageFactory(IEnumerable<BasePackage> basePackages)
public PackageFactory(
IEnumerable<BasePackage> basePackages,
IGithubApiCache githubApiCache,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper,
IPyRunner pyRunner
)
{
this.githubApiCache = githubApiCache;
this.settingsManager = settingsManager;
this.downloadService = downloadService;
this.prerequisiteHelper = prerequisiteHelper;
this.pyRunner = pyRunner;
this.basePackages = basePackages.ToDictionary(x => x.Name);
}
public BasePackage GetNewBasePackage(InstalledPackage installedPackage)
{
return installedPackage.PackageName switch
{
"ComfyUI" => new ComfyUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"Fooocus" => new Fooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"stable-diffusion-webui"
=> new A3WebUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"Fooocus-ControlNet-SDXL"
=> new FocusControlNet(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"Fooocus-MRE"
=> new FooocusMre(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"InvokeAI" => new InvokeAI(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"kohya_ss"
=> new KohyaSs(
githubApiCache,
settingsManager,
downloadService,
prerequisiteHelper,
pyRunner
),
"OneTrainer"
=> new OneTrainer(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"RuinedFooocus"
=> new RuinedFooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"stable-diffusion-webui-forge"
=> new SDWebForge(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"stable-diffusion-webui-directml"
=> new StableDiffusionDirectMl(
githubApiCache,
settingsManager,
downloadService,
prerequisiteHelper
),
"stable-diffusion-webui-ux"
=> new StableDiffusionUx(
githubApiCache,
settingsManager,
downloadService,
prerequisiteHelper
),
"StableSwarmUI"
=> new StableSwarm(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"automatic"
=> new VladAutomatic(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
"voltaML-fast-stable-diffusion"
=> new VoltaML(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
_ => throw new ArgumentOutOfRangeException(nameof(installedPackage))
};
}
public IEnumerable<BasePackage> GetAllAvailablePackages()
{
return basePackages.Values.OrderBy(p => p.InstallerSortOrder).ThenBy(p => p.DisplayName);

3
StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs

@ -7,4 +7,7 @@ public class DownloadPackageVersionOptions
public string? VersionTag { get; set; }
public bool IsLatest { get; set; }
public bool IsPrerelease { get; set; }
public string GetReadableVersionString() =>
!string.IsNullOrWhiteSpace(VersionTag) ? VersionTag : $"{BranchName}@{CommitHash?[..7]}";
}

1
StabilityMatrix.Core/Models/Settings/Settings.cs

@ -120,6 +120,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 IsOutputsTreeViewEnabled { get; set; } = true;
[JsonIgnore]
public bool IsHolidayModeActive =>

1
StabilityMatrix.Core/Models/Settings/TeachingTip.cs

@ -13,6 +13,7 @@ public record TeachingTip(string Value) : StringValue(Value)
public static TeachingTip CheckpointCategoriesTip => new("CheckpointCategoriesTip");
public static TeachingTip PackageExtensionsInstallNotice => new("PackageExtensionsInstallNotice");
public static TeachingTip DownloadsTip => new("DownloadsTip");
public static TeachingTip WebUiButtonMovedTip => new("WebUiButtonMovedTip");
/// <inheritdoc />
public override string ToString()

15
StabilityMatrix.Tests/Helper/PackageFactoryTests.cs

@ -8,31 +8,28 @@ public class PackageFactoryTests
{
private PackageFactory packageFactory = null!;
private IEnumerable<BasePackage> fakeBasePackages = null!;
[TestInitialize]
public void Setup()
{
fakeBasePackages = new List<BasePackage>
{
new DankDiffusion(null!, null!, null!, null!)
};
packageFactory = new PackageFactory(fakeBasePackages);
fakeBasePackages = new List<BasePackage> { new DankDiffusion(null!, null!, null!, null!) };
packageFactory = new PackageFactory(fakeBasePackages, null, null, null, null, null);
}
[TestMethod]
public void GetAllAvailablePackages_ReturnsAllPackages()
{
var result = packageFactory.GetAllAvailablePackages();
Assert.AreEqual(1, result.Count());
}
[TestMethod]
public void FindPackageByName_ReturnsPackage()
{
var result = packageFactory.FindPackageByName("dank-diffusion");
Assert.IsNotNull(result);
}
[TestMethod]
public void FindPackageByName_ReturnsNull()
{

Loading…
Cancel
Save