diff --git a/CHANGELOG.md b/CHANGELOG.md
index 201eb71f5..ea3dd143e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,186 @@ 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.15.0
+### Added
+- Added new package - [AI Toolkit](https://github.com/ostris/ai-toolkit/)
+- Added new package - [FramePack](https://github.com/lllyasviel/FramePack)
+- Added new package - [FramePack Studio](https://github.com/colinurbs/FramePack-Studio)
+- Added Python Version selector for all new package installs
+- Added the ability to rename packages
+- Added support for authenticated model downloads in the HuggingFace model browser. Visit Settings → Accounts to add your HuggingFace token.
+- Added support for dragging-and-dropping Civitai-generated images into Inference to load metadata
+- Added the ability to search by pasting an entire Civitai model URL into the search bar in the Civitai model browser
+- Added "Clear Pip Cache" and "Clear uv Cache" commands to the Settings -> Embedded Python section
+- Added settings to disable base models from appearing in the Checkpoint Manager and Civitai Model Browser base model selectors
+- Added Inference "Favorite Dimensions" quick selector - editable in Settings → Inference, or click the 💾 button inside the dropdown
+- Added setting for Inference dimension step change - the value the dimensions increase or decrease by when using the step buttons or scroll wheel in Inference
+- Added "Install Nunchaku" option to the ComfyUI Package Commands menu
+- Added "Select All" button to the Installed Extensions page
+- Added experimental ROCm pytorch install for ComfyUI (non-Zluda) on Windows - requires a compatible AMD GPU
+- Added base model type labels (SD1.5, SDXL, Flux, etc.) to Inference model selection boxes
+- Added UNET shared folder link for SD.Next
+- Added Manual Install button for installing Package extensions that aren't in the indexes
+- Added Next and Previous buttons to the Civitai details page to navigate between results
+- Added Negative Rejection Steering (NRS) by @reithan to Inference
+- Added Wan 2.2 models to the HuggingFace tab of the model browser
+- Added Tiled Encode/Decode options to FaceDetailer in Inference
+- Added Ukrainian translation thanks to @r0ddty!
+- Added Czech translation thanks to @PEKArt!
+### Changed
+🌟 Civitai Model Details: A Grand Reimagining! 🌟
+- No more peering through a tiny window! Introducing a massive overhaul of the Civitai Model Details page, transforming it from a cramped dialog into a spacious, feature-rich hub for all your model exploration needs.
+- We've listened to your howls for more, and now you can dive deep into every aspect of your favorite models with unprecedented clarity and control:
+ - Expansive View: The new full-page layout means all essential information, descriptions, and previews are laid out beautifully, banishing the old, restrictive dialog forever.
+ - Rich Details at a Glance: Author, base model, last updated, SHA hashes, file name overrides/patterns – everything you need, perfectly organized and always accessible.
+ - Overhauled Image Viewer: Enjoy a sleek, modern image viewer that includes Civitai metadata and supports zooming, panning, and full-screen viewing. No more squinting at tiny thumbnails!
+ - Integrated Inference Options: For supported models, adjust sampler, scheduler, steps, CFG Scale, width, and height directly from the details page, streamlining your workflow like never before!
+----
+- Updated all Python version management, virtual environment creation, and pip installs to use `uv` for improved reliability, compatibility, and speed
+- You can now select release versions when installing ComfyUI
+- You can no longer select branches when installing InvokeAI
+- Updated InvokeAI install to use the intended install method (resolves [#1329](https://github.com/LykosAI/StabilityMatrix/issues/1329))
+- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.3 torch index
+- Updated ComfyUI-Zluda installs to use the newer install-n method (fixes [#1347](https://github.com/LykosAI/StabilityMatrix/issues/1347))
+- Removed disclaimer from reForge since the author is now active again
+- Updated git operations to better avoid conflicts
+- Updated Japanese translation
+- Civitai model browser image loading now uses dynamic resizing for better performance and a smoother scrolling experience
+- Undo ComfyUI process tracking changes for now due to causing more issues than it solved
+- Updated GPU parsing fallback on Linux systems to use the method provided by @irql-notlessorequal
+- New installs of ComfyUI, SD.Next, and InvokeAI will now use Python 3.12, unless otherwise specified in the Advanced Options during installation
+- New installs of all other packages will now use Python 3.10.18, unless otherwise specified in the Advanced Options during installation
+- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.4 torch index
+- Updated package delete confirmation dialog
+### Fixed
+- Fixed an error when packages and other processes exit before process tracking on windows can initialize
+- Fixed "none" appearing in wildcard field when using Face Detailer in Inference
+- Fixed [#1254](https://github.com/LykosAI/StabilityMatrix/issues/1254) - Unable to scroll samplers in Inference
+- Fixed [#1294](https://github.com/LykosAI/StabilityMatrix/issues/1294) - Improper sorting of output folders in Output Browser
+- Fixed [#1300](https://github.com/LykosAI/StabilityMatrix/issues/1300) - Git errors when installing Extension Packs
+- Fixed [#1317](https://github.com/LykosAI/StabilityMatrix/issues/1317) - Inference missing GGUF text encoders
+- Fixed [#1324](https://github.com/LykosAI/StabilityMatrix/issues/1324) - Window height slightly increasing every launch
+- Fixed [#1357](https://github.com/LykosAI/StabilityMatrix/issues/1357) - Case insensitivity causing duplicate key exceptions on non-Windows systems
+- Fixed [#1360](https://github.com/LykosAI/StabilityMatrix/issues/1360) - A1111 install not using correct torch for 5000-series GPUs
+- Fixed [#1361](https://github.com/LykosAI/StabilityMatrix/issues/1361) - numpy and other Forge startup
+- Fixed [#1365](https://github.com/LykosAI/StabilityMatrix/issues/1365) - Output folder list not updating when Refresh button clicked
+### Supporters
+#### 🌟 Visionaries
+To our incredible Visionaries, the architects of our ambition: Your profound support is the powerhouse behind this massive v2.15.0 release. You don't just light the path; you fuel the entire journey, allowing us to build bigger, move faster, and turn bold ideas into reality. Our deepest gratitude to: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, **whudunit**, and **TheTekknician**! We are immensely grateful for your trust and partnership in shaping the future of Stability Matrix. Thank you for everything!
+#### 🚀 Pioneers
+A heartfelt salute to our trailblazing Pioneers! Your consistent support helps us navigate the development landscape, ensuring we stay on the right track and can explore new frontiers. A huge thanks to: **tankfox**, **Mr. Unknown**, **Szir777**, **Tigon**, **Noah M**, **USATechDude**, **Thom**, **SeraphOfSalem**, and a special welcome to our newest Pioneers - **Desert Viber**, **Tundra Everquill**, **Adam**, and **Droolguy**! Thank you for being the vanguard of our community!
+
+## v2.15.0-pre.2
+### Added
+- Added new package - [AI Toolkit](https://github.com/ostris/ai-toolkit/)
+- Added Manual Install button for installing Package extensions that aren't in the indexes
+- Added Next and Previous buttons to the Civitai details page to navigate between results
+- Added Negative Rejection Steering (NRS) by @reithan to Inference
+- Added Czech translation thanks to @PEKArt!
+- Added Wan 2.2 models to the HuggingFace tab of the model browser
+- Added Tiled Encode/Decode options to FaceDetailer in Inference
+### Changed
+- Brought back the "size remaining after download" tooltip in the new Civitai details page
+- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.4 torch index
+- Updated package delete confirmation dialog
+### Fixed
+- Fixed Inference custom step (e.g. HiresFix) Samplers potentially sharing state with other card UIs like model browser.
+- Fixed extension manager failing to install extensions due to incorrect clone directory
+- Fixed duplicate Python versions appearing in the Advanced Options when installing a package
+- Fixed an error when packages and other processes exit before process tracking on windows can initialize
+- Fixed "none" appearing in wildcard field when using Face Detailer in Inference
+- Fixed [#1254](https://github.com/LykosAI/StabilityMatrix/issues/1254) - Unable to scroll samplers in Inference
+- Fixed [#1294](https://github.com/LykosAI/StabilityMatrix/issues/1294) - Improper sorting of output folders in Output Browser
+- Fixed [#1300](https://github.com/LykosAI/StabilityMatrix/issues/1300) - Git errors when installing Extension Packs
+- Fixed [#1317](https://github.com/LykosAI/StabilityMatrix/issues/1317) - Inference missing GGUF text encoders
+- Fixed [#1324](https://github.com/LykosAI/StabilityMatrix/issues/1324) - Window height slightly increasing every launch
+- Fixed [#1360](https://github.com/LykosAI/StabilityMatrix/issues/1360) - A1111 install not using correct torch for 5000-series GPUs
+- Fixed [#1361](https://github.com/LykosAI/StabilityMatrix/issues/1361) - numpy and other Forge startup
+### Supporters
+#### 🌟 Visionaries
+A huge thank-you to our incredible Visionary-tier supporters: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, **whudunit**, and **Akiro_Senkai**! Your continued support lights the way for Stability Matrix and helps us keep building features like these. We couldn’t do it without you.
+
+## v2.15.0-pre.1
+### Added
+- Added settings to disable base models from appearing in the Checkpoint Manager and Civitai Model Browser base model selectors
+- Added Inference "Favorite Dimensions" quick selector - editable in Settings → Inference, or click the 💾 button inside the dropdown
+- Added setting for Inference dimension step change - the value the dimensions increase or decrease by when using the step buttons or scroll wheel in Inference
+- Added "Install Nunchaku" option to the ComfyUI Package Commands menu
+- Added "Select All" button to the Installed Extensions page
+- Added experimental ROCm pytorch install for ComfyUI (non-Zluda) on Windows - requires a compatible AMD GPU
+- Added base model type labels (SD1.5, SDXL, Flux, etc.) to Inference model selection boxes
+- Added UNET shared folder link for SD.Next
+- Added Ukrainian translation thanks to @r0ddty!
+### Changed
+🌟 Civitai Model Details: A Grand Reimagining! 🌟
+ - No more peering through a tiny window! Introducing a massive overhaul of the Civitai Model Details page, transforming it from a cramped dialog into a spacious, feature-rich hub for all your model exploration needs.
+ - We've listened to your howls for more, and now you can dive deep into every aspect of your favorite models with unprecedented clarity and control:
+ - Expansive View: The new full-page layout means all essential information, descriptions, and previews are laid out beautifully, banishing the old, restrictive dialog forever.
+ - Rich Details at a Glance: Author, base model, last updated, SHA hashes, file name overrides/patterns – everything you need, perfectly organized and always accessible.
+ - Overhauled Image Viewer: Enjoy a sleek, modern image viewer that includes Civitai metadata and supports zooming, panning, and full-screen viewing. No more squinting at tiny thumbnails!
+ - Integrated Inference Options: For supported models, adjust sampler, scheduler, steps, CFG Scale, width, and height directly from the details page, streamlining your workflow like never before!
+----
+- You can now select release versions when installing ComfyUI
+- You can no longer select branches when installing InvokeAI
+- Updated InvokeAI install to use pinned torch index from release tag
+- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.3 torch index
+- Updated ComfyUI-Zluda installs to use the newer install-n method (fixes [#1347](https://github.com/LykosAI/StabilityMatrix/issues/1347))
+- Updated uv to 0.8.4
+- Removed disclaimer from reForge since the author is now active again
+- Updated git operations to better avoid conflicts
+- Updated Japanese translation
+- Undo ComfyUI process tracking changes for now due to causing more issues than it solved
+- Updated GPU parsing fallback on Linux systems to use the method provided by @irql-notlessorequal
+### Fixed
+- Fixed Civitai-generated image parsing in Inference
+- Fixed some first-time setup crashes from missing prerequisites
+- Fixed one-click installer not using default preferred Python version
+- Fixed updating from old installs of InvokeAI using old frontend
+- Fixed [#1357](https://github.com/LykosAI/StabilityMatrix/issues/1357) - Case insensitivity causing duplicate key exceptions on non-Windows systems
+### Supporters
+#### 🌟 Visionaries
+To our brilliant Visionary-tier Patrons: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, and **whudunit** — your support is the spark that keeps Stability Matrix blazing forward. Thanks to you, we can explore bolder features, tackle complex challenges, and keep making the impossible feel effortless. Thank you all so very much! 🚀
+
+## v2.15.0-dev.2
+### Added
+- Added new package - [FramePack](https://github.com/lllyasviel/FramePack)
+- Added new package - [FramePack Studio](https://github.com/colinurbs/FramePack-Studio)
+- Added support for authenticated model downloads in the HuggingFace model browser. Visit Settings → Accounts to add your HuggingFace token.
+- Added support for dragging-and-dropping Civitai-generated images into Inference to load metadata
+- Added the ability to search by pasting an entire Civitai model URL into the search bar in the Civitai model browser (when the Civitai API gets fixed)
+- Added "Clear Pip Cache" and "Clear uv Cache" commands to the Settings -> Embedded Python section
+### Changed
+- Civitai model browser image loading now uses dynamic resizing for better performance and a smoother scrolling experience
+- Detailed notifications for Civitai model browser api errors
+- The main sidebar now remembers whether it was collapsed or expanded between restarts
+- Updated pre-selected download locations for certain model types in the Civitai model browser
+- Updated uv to 0.7.19
+- Changed InvokeAI update process to no longer clone the repo
+### Fixed
+- Fixed missing .NET 8 dependency for SwarmUI installs in certain cases
+- Fixed [#1291](https://github.com/LykosAI/StabilityMatrix/issues/1291) - Certain GPUs not being detected on Linux
+- Fixed [#1284](https://github.com/LykosAI/StabilityMatrix/issues/1284) - Output browser not ignoring InvokeAI thumbnails folders
+- Fixed [#1305](https://github.com/LykosAI/StabilityMatrix/issues/1305) - FluxGym installing incorrect packages for Blackwell GPUs
+- Fixed [#1316](https://github.com/LykosAI/StabilityMatrix/issues/1316) - Errors when installing Triton & SageAttention
+- Fixed "directory is not empty" error when updating packages with symlinks
+- Fixed missing base model types in the Checkpoint Manager & Civitai Model Browser
+### Supporters
+#### 🌟 Visionaries
+A huge thank you to our amazing Visionary-tier Patrons: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, and our newest Visionary, **whudunit**! 🚀 Your generous support enables Stability Matrix to grow faster and tackle ambitious new ideas. You're truly making all the magic happen!
+
+## v2.15.0-dev.1
+### Added
+- Added Python Version selector for all new package installs
+- Added the ability to rename packages
+### Changed
+- Updated all Python version management, virtual environment creation, and pip installs to use `uv` for improved reliability, compatibility, and speed
+- The Civitai model browser Download Location selector will now remember the last location used based on the model type
+- New installs of ComfyUI, SD.Next, and InvokeAI will now use Python 3.12.10, unless otherwise specified in the Advanced Options during installation
+- New installs of all other packages will now use Python 3.10.17, unless otherwise specified in the Advanced Options during installation
+### Supporters
+#### 🌟 Visionaries
+A massive thank you to our esteemed Visionary-tier Patrons: **Waterclouds**, **bluepopsicle**, **Bob S**, **Ibixat**, and **Corey T**! Your exceptional commitment propels Stability Matrix to new heights and allows us to push the boundaries of innovation. We're incredibly grateful for your foundational support! 🚀
+
## v2.14.3
### Added
- Added the ability to search by pasting an entire Civitai model URL into the search bar in the Civitai model browser
diff --git a/Directory.Build.props b/Directory.Build.props
index d09384c92..3bca99c42 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -10,7 +10,7 @@
- 11.2.5
+ 11.3.2
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9f12d0225..164562047 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -79,6 +79,7 @@
+
diff --git a/README.md b/README.md
index a245b4029..366591253 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,7 @@ Stability Matrix is now available in the following languages, thanks to our comm
- 🇷🇺 Русский
- aolko
- den1251
+ - vanja-san
- 🇹🇷 Türkçe
- Progesor
- 🇩🇪 Deutsch
@@ -143,6 +144,10 @@ Stability Matrix is now available in the following languages, thanks to our comm
- thiagojramos
- 🇰🇷 한국어
- maakcode
+- 🇺🇦 Українська
+ - rodtty
+- 🇨🇿 Čeština
+ - PEKArt!
If you would like to contribute a translation, please create an issue or contact us on Discord. Include an email where we'll send an invite to our [POEditor](https://poeditor.com/) project.
diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml
index b3c215bc0..fe088a5be 100644
--- a/StabilityMatrix.Avalonia/App.axaml
+++ b/StabilityMatrix.Avalonia/App.axaml
@@ -99,6 +99,7 @@
+
+
+
diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs
index c540620d6..e31445507 100644
--- a/StabilityMatrix.Avalonia/App.axaml.cs
+++ b/StabilityMatrix.Avalonia/App.axaml.cs
@@ -63,6 +63,7 @@
using StabilityMatrix.Core.Models.Configs;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Settings;
+using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
using StabilityMatrix.Core.Updater;
using ApiOptions = StabilityMatrix.Core.Models.Configs.ApiOptions;
@@ -719,6 +720,15 @@ internal static IServiceCollection ConfigureServices(bool disableMessagePipeInte
})
.AddPolicyHandler(retryPolicy);
+ services
+ .AddRefitClient(defaultRefitSettings) // Assuming defaultRefitSettings is suitable
+ .ConfigureHttpClient(c =>
+ {
+ c.BaseAddress = new Uri("https://huggingface.co");
+ c.Timeout = TimeSpan.FromHours(1); // Or a more appropriate timeout like 60 seconds, consistent with retry policy
+ })
+ .AddPolicyHandler(retryPolicy); // Assuming retryPolicy is suitable
+
// Apizr clients
services.AddApizrManagerFor(options =>
{
diff --git a/StabilityMatrix.Avalonia/Assets/brands-hf-logo.png b/StabilityMatrix.Avalonia/Assets/brands-hf-logo.png
new file mode 100644
index 000000000..f2c70c12c
Binary files /dev/null and b/StabilityMatrix.Avalonia/Assets/brands-hf-logo.png differ
diff --git a/StabilityMatrix.Avalonia/Assets/hf-packages.json b/StabilityMatrix.Avalonia/Assets/hf-packages.json
index f9249de8c..c3dd9c135 100644
--- a/StabilityMatrix.Avalonia/Assets/hf-packages.json
+++ b/StabilityMatrix.Avalonia/Assets/hf-packages.json
@@ -1085,6 +1085,87 @@
],
"LicenseType": "Apache 2.0"
},
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 TI2V 5B fp16",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_ti2v_5B_fp16.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 I2V High Noise 14B fp16",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp16.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 I2V High Noise 14B fp8",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 I2V Low Noise 14B fp16",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp16.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 I2V Low Noise 14B fp8",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 T2V High Noise 14B fp16",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp16.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 T2V High Noise 14B fp8",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 T2V Low Noise 14B fp16",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp16.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
+ {
+ "ModelCategory": "Unet",
+ "ModelName": "Wan 2.2 T2V Low Noise 14B fp8",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
{
"ModelCategory": "Unet",
"ModelName": "HiDream I1 Dev bf16",
@@ -1146,7 +1227,8 @@
"Files": [
"ae.safetensors"
],
- "LicenseType": "Apache 2.0"
+ "LicenseType": "Apache 2.0",
+ "LoginRequired": true
},
{
"ModelCategory": "Vae",
@@ -1157,6 +1239,15 @@
],
"LicenseType": "Apache 2.0"
},
+ {
+ "ModelCategory": "Vae",
+ "ModelName": "Wan 2.2 VAE",
+ "RepositoryPath": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
+ "Files": [
+ "split_files/vae/wan2.2_vae.safetensors"
+ ],
+ "LicenseType": "Apache 2.0"
+ },
{
"ModelCategory": "Vae",
"ModelName": "HiDream I1 VAE",
diff --git a/StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml.cs b/StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml.cs
index 4e1706f02..005fbc5c8 100644
--- a/StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml.cs
@@ -163,7 +163,7 @@ public ZoomLevelCollection(IEnumerable collection)
5800,
6800,
7800,
- 8800
+ 8800,
}
);
@@ -426,7 +426,7 @@ public enum SizeModes : byte
///
/// The image is stretched to fill as much of the client area of the control as possible, whilst retaining the same aspect ratio for the width and height.
///
- Fit
+ Fit,
}
[Flags]
@@ -435,7 +435,7 @@ public enum MouseButtons : byte
None = 0,
LeftButton = 1,
MiddleButton = 2,
- RightButton = 4
+ RightButton = 4,
}
///
@@ -462,7 +462,7 @@ public enum ZoomActions : byte
///
/// The control zoom was reset.
///
- ActualSize = 4
+ ActualSize = 4,
}
public enum SelectionModes
@@ -480,7 +480,7 @@ public enum SelectionModes
///
/// Zoom selection.
///
- Zoom
+ Zoom,
}
#endregion
@@ -511,7 +511,7 @@ public Vector Offset
}
}
- public Size ViewPortSize => ViewPort.Bounds.Size;
+ public Size ViewPortSize => ViewPort?.Bounds.Size ?? new Size(50, 50);
#endregion
#region Private Members
@@ -1355,13 +1355,13 @@ private void RenderBackgroundGrid(DrawingContext context)
var square1Drawing = new GeometryDrawing
{
Brush = GridColorAlternate,
- Geometry = new RectangleGeometry(new Rect(0.0, 0.0, size, size))
+ Geometry = new RectangleGeometry(new Rect(0.0, 0.0, size, size)),
};
var square2Drawing = new GeometryDrawing
{
Brush = GridColorAlternate,
- Geometry = new RectangleGeometry(new Rect(size, size, size, size))
+ Geometry = new RectangleGeometry(new Rect(size, size, size, size)),
};
var drawingGroup = new DrawingGroup { Children = { square1Drawing, square2Drawing } };
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml
index 373d85285..b3d7c50e7 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml
@@ -133,6 +133,33 @@
DataContext="{Binding SeedCardViewModel}"
IsVisible="{Binding !$parent[StackPanel].((vmInference:FaceDetailerViewModel)DataContext).InheritSeed, TargetNullValue=False, FallbackValue=False}" />
+
+
+
+
+
+
+
+
+
+
@@ -159,7 +186,7 @@
@@ -440,6 +467,9 @@
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml
new file mode 100644
index 000000000..8edc75668
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml.cs
new file mode 100644
index 000000000..16764ecee
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml.cs
@@ -0,0 +1,6 @@
+using Injectio.Attributes;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+[RegisterTransient]
+public class NrsCard : TemplatedControlBase { }
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml
index 52229ef01..f6d4f0a0e 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml
@@ -486,7 +486,8 @@
+ DataContext="{Binding ModulesCardViewModel}"
+ IsVisible="{Binding $parent[Grid].((vmInference:PromptCardViewModel)DataContext).IsStackCardEnabled}" />
@@ -30,7 +38,7 @@
VerticalAlignment="Center"
IsVisible="{Binding IsSamplerSelectionEnabled}"
Text="{x:Static lang:Resources.Label_Sampler}" />
-
-
+ RowDefinitions="Auto,*,Auto">
@@ -217,7 +225,7 @@
Margin="4,0,0,0"
HorizontalAlignment="Stretch"
PlaceholderText="128"
- SmallChange="128"
+ SmallChange="{Binding DimensionStepChange}"
SpinButtonPlacementMode="Compact"
ValidationMode="InvalidInputOverwritten"
Value="{Binding Height}" />
@@ -235,6 +243,90 @@
FontSize="18"
Symbol="RepeatAll" />
+
+
diff --git a/StabilityMatrix.Avalonia/Converters/CivitImageWidthConverter.cs b/StabilityMatrix.Avalonia/Converters/CivitImageWidthConverter.cs
new file mode 100644
index 000000000..d5a109d63
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Converters/CivitImageWidthConverter.cs
@@ -0,0 +1,149 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using Avalonia;
+using Avalonia.Data.Converters;
+
+namespace StabilityMatrix.Avalonia.Converters;
+
+[Localizable(false)]
+public class CivitImageWidthConverter : IValueConverter
+{
+ private const string CivitaiHost = "image.civitai.com";
+ private const string WidthKey = "width";
+
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ // --- Input Validation ---
+ Uri? inputUri = null;
+ if (value is string urlString && Uri.TryCreate(urlString, UriKind.Absolute, out var parsedUri))
+ {
+ inputUri = parsedUri;
+ }
+ else if (value is Uri uri)
+ {
+ inputUri = uri;
+ }
+
+ // Check if it's a valid Civitai URL
+ if (
+ inputUri == null
+ || !inputUri.IsAbsoluteUri
+ || !inputUri.Host.Equals(CivitaiHost, StringComparison.OrdinalIgnoreCase)
+ )
+ {
+ // Not a valid Civitai URL, return original value or UnsetValue
+ return value is Uri ? value : AvaloniaProperty.UnsetValue;
+ }
+
+ // Check and parse the ConverterParameter for the target width
+ if (
+ parameter == null
+ || !int.TryParse(
+ parameter.ToString(),
+ NumberStyles.Integer,
+ CultureInfo.InvariantCulture,
+ out var targetWidth
+ )
+ || targetWidth <= 0
+ )
+ {
+ // Invalid or missing width parameter, return original URI
+ return inputUri;
+ }
+
+ // --- URL Modification ---
+ try
+ {
+ var builder = new UriBuilder(inputUri);
+ var pathSegments = builder.Path.Split('/', StringSplitOptions.RemoveEmptyEntries).ToList();
+
+ if (pathSegments.Count < 2) // Need at least /{guid}/{filename} or similar structure
+ {
+ // Path structure isn't recognized, return original
+ return inputUri;
+ }
+
+ // Assume the transformation segment is the second to last one
+ var transformSegmentIndex = pathSegments.Count - 2;
+ var potentialTransformSegment = pathSegments[transformSegmentIndex];
+
+ var transformSegmentFoundAndUpdated = false;
+ var parameters = ParseTransformSegment(potentialTransformSegment);
+
+ if (parameters != null) // It looks like a transformation segment
+ {
+ var widthExists = parameters.ContainsKey(WidthKey);
+ parameters[WidthKey] = targetWidth.ToString(CultureInfo.InvariantCulture); // Update or add width
+
+ // Reconstruct the segment
+ pathSegments[transformSegmentIndex] = string.Join(
+ ",",
+ parameters.Select(kvp => $"{kvp.Key}={kvp.Value}")
+ );
+ transformSegmentFoundAndUpdated = true;
+ }
+
+ if (!transformSegmentFoundAndUpdated)
+ {
+ // Transformation segment not found or didn't parse correctly, insert a new width segment
+ pathSegments.Insert(
+ pathSegments.Count - 1,
+ $"{WidthKey}={targetWidth.ToString(CultureInfo.InvariantCulture)}"
+ );
+ }
+
+ // Reconstruct the path. UriBuilder.Path needs leading '/'
+ builder.Path = "/" + string.Join("/", pathSegments);
+
+ // Return the modified Uri
+ // Check if the target type prefers string or Uri
+ if (targetType == typeof(string))
+ {
+ return builder.Uri.ToString();
+ }
+ return builder.Uri;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Error converting Civitai URL: {ex.Message}");
+
+ // Return original URI on error
+ return inputUri;
+ }
+ }
+
+ // Simple parser for "key1=value1,key2=value2" format
+ private Dictionary? ParseTransformSegment(string segment)
+ {
+ // Basic check: must contain '=' and not be just a filename (heuristic: no '.')
+ if (!segment.Contains('=') || segment.Contains('.'))
+ {
+ return null;
+ }
+
+ var parameters = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var pairs = segment.Split(',');
+
+ foreach (var pair in pairs)
+ {
+ var keyValue = pair.Split('=', 2); // Split only on the first '='
+ if (keyValue.Length == 2)
+ {
+ parameters[keyValue[0].Trim()] = keyValue[1].Trim();
+ }
+ else
+ {
+ // Invalid pair format, assume segment is not a transform segment
+ return null;
+ }
+ }
+ // If we successfully parsed at least one pair, return the dictionary
+ return parameters.Count > 0 ? parameters : null;
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(CivitImageWidthConverter)} does not support ConvertBack.");
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Converters/FileUriConverter.cs b/StabilityMatrix.Avalonia/Converters/FileUriConverter.cs
index 751f5368f..c95d547f4 100644
--- a/StabilityMatrix.Avalonia/Converters/FileUriConverter.cs
+++ b/StabilityMatrix.Avalonia/Converters/FileUriConverter.cs
@@ -1,4 +1,3 @@
-using System;
using System.Globalization;
using Avalonia.Data.Converters;
using StabilityMatrix.Core.Extensions;
@@ -18,9 +17,10 @@ public class FileUriConverter : IValueConverter
return value switch
{
string str when str.StartsWith("avares://") => new Uri(str),
+ string str when (str.StartsWith("https://") || str.StartsWith("http://")) => new Uri(str),
string str => new Uri("file://" + str),
IFormattable formattable => new Uri("file://" + formattable.ToString(null, culture)),
- _ => null
+ _ => null,
};
}
diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
index c5e6828f8..9bbbecebf 100644
--- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs
+++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
@@ -7,6 +7,7 @@
using System.Linq;
using System.Net.Http;
using System.Text;
+using AvaloniaEdit.Document;
using AvaloniaEdit.Utils;
using DynamicData.Binding;
using Microsoft.Extensions.DependencyInjection;
@@ -28,13 +29,16 @@
using StabilityMatrix.Avalonia.ViewModels.PackageManager;
using StabilityMatrix.Avalonia.ViewModels.Progress;
using StabilityMatrix.Avalonia.ViewModels.Settings;
+using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Api;
using StabilityMatrix.Core.Database;
+using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
+using StabilityMatrix.Core.Models.Api.CivitTRPC;
using StabilityMatrix.Core.Models.Api.Comfy;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Models.Api.OpenModelsDb;
@@ -83,7 +87,7 @@ public static void Initialize()
{
Id = activePackageId,
DisplayName = "My Installed Package",
- PackageName = "stable-diffusion-webui",
+ PackageName = "framepack",
Version = new InstalledPackageVersion { InstalledReleaseVersion = "v1.0.0" },
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now,
@@ -208,11 +212,38 @@ public static void Initialize()
null,
null,
null,
- null,
packageFactory,
+ null,
null
);
+ PackageInstallDetailViewModel.AvailablePythonVersions = new ObservableCollection(
+ [
+ new UvPythonInfo(
+ PyInstallationManager.Python_3_10_17,
+ "C:\\SMData\\Data\\Data\\Assets\\Python\\cpython-3.11.12-windows-x86_64-none",
+ true,
+ "cpython",
+ "x86_64",
+ "windows",
+ "cpython-3.10.17-windows-x86_64-none",
+ "default",
+ "none"
+ ),
+ new UvPythonInfo(
+ PyInstallationManager.Python_3_12_10,
+ null,
+ false,
+ "cpython",
+ "x86_64",
+ "windows",
+ "guh I can't be bothered",
+ "freethreaded",
+ "none"
+ ),
+ ]
+ );
+
/*ObservableCacheEx.AddOrUpdate(
OldCheckpointsPageViewModel.CheckpointFoldersCache,
new CheckpointFolder[]
@@ -644,8 +675,23 @@ public static MainPackageManagerViewModel MainPackageManagerViewModel
public static InferenceSettingsViewModel InferenceSettingsViewModel =>
Services.GetRequiredService();
- public static MainSettingsViewModel MainSettingsViewModel =>
- Services.GetRequiredService();
+ public static MainSettingsViewModel MainSettingsViewModel
+ {
+ get
+ {
+ var vm = Services.GetRequiredService();
+ vm.AllBaseModelTypes = new List()
+ {
+ CivitBaseModelType.WanVideo.GetStringValue(),
+ CivitBaseModelType.Sdxl10.GetStringValue(),
+ CivitBaseModelType.Flux1D.GetStringValue(),
+ "Flux 1. Kontext",
+ }
+ .Select(s => new BaseModelOptionViewModel { ModelType = s, IsSelected = true })
+ .ToList();
+ return vm;
+ }
+ }
public static AccountSettingsViewModel AccountSettingsViewModel =>
Services.GetRequiredService();
@@ -690,6 +736,91 @@ public static UpdateSettingsViewModel UpdateSettingsViewModel
public static CheckpointBrowserViewModel CheckpointBrowserViewModel =>
Services.GetRequiredService();
+ public static CivitDetailsPageViewModel CivitDetailsPageViewModel =>
+ DialogFactory.Get(vm =>
+ {
+ vm.CivitModel = new CivitModel
+ {
+ Name = "BB95 Furry Mix",
+ Description = "A furry mix of BB95",
+ Stats = new CivitModelStats
+ {
+ Rating = 3.5,
+ RatingCount = 24,
+ ThumbsUpCount = 1337,
+ DownloadCount = 100_000,
+ },
+ Tags = ["base model", "furry", "animals", "photorealistic", "highly detailed", "yiff"],
+ ModelVersions =
+ [
+ new CivitModelVersion
+ {
+ Name = "v1.2.2-Inpainting",
+ PublishedAt = DateTimeOffset.Now,
+ Images =
+ [
+ new CivitImage
+ {
+ Url =
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/244bc929-9bbc-44b2-8a26-7622a1669f4a/original=true,quality=90/00123-3430906941-1girl,%20hatsune%20miku,%20white%20pupils,%20power%20elements,%20microphone,%20vibrant%20blue%20color%20palette,%20abstract,abstract%20background,%20dreamli.jpeg",
+ },
+ new CivitImage
+ {
+ Url =
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/aa671302-496f-4dcc-839a-b75a70a7665e/original=true,quality=90/00136-4204868169-1girl,%20solo,%20long%20hair,%20city,%20boots,%20hands%20in%20pockets,%20coat,%20blonde%20hair,%20sky,%20planet,%20night,%20knee%20boots,%20building,%20star%20_(sky_).jpeg",
+ },
+ ],
+ Files =
+ [
+ new CivitFile
+ {
+ Name = "bb95-v100-uwu-reallylongfilename-v1234576802.safetensors",
+ Type = CivitFileType.Model,
+ Metadata = new CivitFileMetadata
+ {
+ Format = CivitModelFormat.SafeTensor,
+ Fp = "fp16",
+ Size = "pruned",
+ },
+ },
+ new CivitFile
+ {
+ Name = "bb95-v100-uwu-reallylongfilename-v1234576802-fp32.safetensors",
+ Type = CivitFileType.Model,
+ Metadata = new CivitFileMetadata
+ {
+ Format = CivitModelFormat.SafeTensor,
+ Fp = "fp32",
+ Size = "full",
+ },
+ Hashes = new CivitFileHashes
+ {
+ BLAKE3 =
+ "A7383E54F2E4570678B0F18545B2EB8FD95325DA76CCBA8467DBDBD481CF6B99",
+ SHA256 =
+ "BDB59BAC77D94AE7A55FF893170F9554C3F349E48A1B73C0C17C0B7C6F4D41A2",
+ },
+ },
+ ],
+ },
+ new CivitModelVersion { Name = "v1.2.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v1.1.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v1.0.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v0.9.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v0.8.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v0.7.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v0.6.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v0.5.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ new CivitModelVersion { Name = "v0.4.0", PublishedAt = DateTimeOffset.Now.AddDays(-3) },
+ ],
+ Creator = new CivitCreator
+ {
+ Image = "https://gravatar.com/avatar/fe74084ae8a081dc2283f5bde4736756ad?f=y&d=retro",
+ Username = "creator-1",
+ },
+ };
+ });
+
public static SelectModelVersionViewModel SelectModelVersionViewModel =>
DialogFactory.Get(vm =>
{
@@ -937,6 +1068,8 @@ public static UpdateSettingsViewModel UpdateSettingsViewModel
public static FreeUCardViewModel FreeUCardViewModel => DialogFactory.Get();
+ public static NrsCardViewModel NrsCardViewModel => DialogFactory.Get();
+
public static PromptCardViewModel PromptCardViewModel =>
DialogFactory.Get(vm =>
{
@@ -1001,6 +1134,16 @@ public static UpdateSettingsViewModel UpdateSettingsViewModel
public static ExtraNetworkCardViewModel ExtraNetworkCardViewModel =>
DialogFactory.Get();
+ public static ConfirmPackageDeleteDialogViewModel ConfirmPackageDeleteDialogViewModel =>
+ DialogFactory.Get(vm =>
+ vm.Package = new InstalledPackage
+ {
+ PackageName = "a1111",
+ DisplayName = "Automatic1111 WebUI",
+ LibraryPath = "packages\\a1111",
+ }
+ );
+
public static InstalledWorkflowsViewModel InstalledWorkflowsViewModel
{
get
@@ -1137,6 +1280,12 @@ public static CompletionList SampleCompletionList
{
ModelName = "Art Shaper (very long name example)",
VersionName = "Style v8 (very long name)",
+ ModelId = 0,
+ VersionId = 0,
+ ModelDescription =
+ "This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.",
+ ModelType = CivitModelType.Checkpoint,
+ BaseModel = "SD 1.5",
},
}
),
@@ -1151,6 +1300,12 @@ public static CompletionList SampleCompletionList
{
ModelName = "Background Arts",
VersionName = "Anime Style v10",
+ ModelId = 0,
+ VersionId = 0,
+ ModelDescription =
+ "This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.",
+ ModelType = CivitModelType.Checkpoint,
+ BaseModel = "SDXL 1.0",
},
}
),
@@ -1171,6 +1326,43 @@ public static CompletionList SampleCompletionList
vm.FileNameText = "TextToImage_00041.png";
vm.FileSizeText = "2.4 MB";
vm.ImageSizeText = "1280 x 1792";
+
+ vm.CivitImageMetadata = new CivitImageGenerationDataResponse
+ {
+ Metadata = new CivitImageMetadata
+ {
+ Prompt =
+ "closeup photp of a red haired anthro wolf female,\n holding an apple, wearing medieval drees is eating a apple, wolf ears, wolf tail with white tip\n,anthro,furry",
+ NegativePrompt = "Bad quality , watermark",
+ CfgScale = 2.5d,
+ Steps = 30,
+ Sampler = "DPM++ SDE",
+ Seed = 255842256659122,
+ Model = "RatatoskrIllustriousV2.3",
+ Height = 1152,
+ Width = 768,
+ Scheduler = "normal",
+ },
+ Resources =
+ [
+ new CivitImageResource
+ {
+ ModelName = "noobAI XL (NAI-XL) really long name example with even more words",
+ ModelId = 1337,
+ VersionId = 1234,
+ VersionName = "Epsilon-pred 1.1-Version",
+ ModelType = "Checkpoint",
+ },
+ ],
+ };
+
+ vm.CivitImageMetadata.OtherMetadata = new Dictionary
+ {
+ ["CFG"] = "2.5",
+ ["Steps"] = "30",
+ ["Sampler"] = "DPM++ SDE",
+ ["Seed"] = "255842256659122",
+ };
});
public static DownloadResourceViewModel DownloadResourceViewModel =>
@@ -1224,6 +1416,42 @@ public static CompletionList SampleCompletionList
.ToArray();
});
+ public static ConfirmBulkDownloadDialogViewModel ConfirmBulkDownloadDialogViewModel =>
+ DialogFactory.Get(vm =>
+ {
+ vm.Model = new CivitModel
+ {
+ Name = "Test Model",
+ ModelVersions = Enumerable
+ .Range(1, 64)
+ .Select(i => new CivitModelVersion
+ {
+ Name = $"Version {i}",
+ Files =
+ [
+ new CivitFile
+ {
+ Name = $"test-file-{i}.safetensors",
+ Type = CivitFileType.Model,
+ Metadata = new CivitFileMetadata
+ {
+ Format = CivitModelFormat.SafeTensor,
+ Fp = "fp16",
+ Size = "pruned",
+ },
+ SizeKb = new Random().Next(1, 10) * 1024 * 1024,
+ },
+ ],
+ })
+ .ToList(),
+ };
+
+ vm.FpTypePreference = CivitModelFpType.fp16;
+ vm.IncludeVae = true;
+
+ return vm;
+ });
+
public static SponsorshipPromptViewModel SponsorshipPromptViewModel =>
DialogFactory.Get(vm => { });
@@ -1312,6 +1540,25 @@ public static CompletionList SampleCompletionList
vm.BaseModelType = "Pony";
});
+ public static PackageInstallProgressItemViewModel PackageInstallProgressItemViewModel =>
+ new(
+ new PackageModificationRunner
+ {
+ CurrentProgress = new ProgressReport(50, "Installing Package", "Description"),
+ ModificationCompleteMessage = "Install Complete",
+ }
+ )
+ {
+ Progress = new ContentDialogProgressViewModelBase
+ {
+ Value = 50,
+ CloseWhenFinished = true,
+ Text = "Installing Package",
+ Description = "Description",
+ Console = { Document = new TextDocument("Hello world") },
+ },
+ };
+
public static MockGitVersionProvider MockGitVersionProvider => new();
public static string CurrentDirectory => Directory.GetCurrentDirectory();
diff --git a/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs b/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs
index 7b203754c..b6b406c95 100644
--- a/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs
@@ -36,13 +36,13 @@ IServiceManager dialogFactory
public override BasePackage? SelectedBasePackage =>
SelectedPackage?.PackageName != "dank-diffusion"
? base.SelectedBasePackage
- : new DankDiffusion(null!, null!, null!, null!);
+ : new DankDiffusion(null!, null!, null!, null!, null!);
protected override Task LaunchImpl(string? command)
{
IsLaunchTeachingTipsOpen = false;
- RunningPackage = new PackagePair(null!, new DankDiffusion(null!, null!, null!, null!));
+ RunningPackage = new PackagePair(null!, new DankDiffusion(null!, null!, null!, null!, null!));
Console.Document.Insert(
0,
diff --git a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
index 385eecba0..47ed99164 100644
--- a/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
+++ b/StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
@@ -1,5 +1,6 @@
using Nito.Disposables.Internals;
using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Services;
@@ -11,17 +12,68 @@ public class MockModelIndexService : IModelIndexService
public Dictionary> ModelIndex { get; } =
new()
{
+ [SharedFolderType.StableDiffusion] =
+ [
+ new LocalModelFile
+ {
+ SharedFolderType = SharedFolderType.StableDiffusion,
+ RelativePath = "art_shaper_v8.safetensors",
+ PreviewImageFullPath =
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512/img.jpeg",
+ ConnectedModelInfo = new ConnectedModelInfo
+ {
+ ModelName = "Art Shaper (very long name example)",
+ VersionName = "Style v8 (very long name)",
+ ModelId = 0,
+ VersionId = 0,
+ ModelDescription =
+ "This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.",
+ ModelType = CivitModelType.Checkpoint,
+ BaseModel = "SD 1.5",
+ },
+ },
+ new LocalModelFile
+ {
+ SharedFolderType = SharedFolderType.StableDiffusion,
+ RelativePath = "background_arts.safetensors",
+ PreviewImageFullPath =
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/71c81ddf-d8c3-46b4-843d-9f8f20a9254a/width=512/img.jpeg",
+ ConnectedModelInfo = new ConnectedModelInfo
+ {
+ ModelName = "Background Arts",
+ VersionName = "Anime Style v10",
+ ModelId = 0,
+ VersionId = 0,
+ ModelDescription =
+ "This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.",
+ ModelType = CivitModelType.Checkpoint,
+ BaseModel = "SDXL 1.0",
+ },
+ },
+ ],
[SharedFolderType.Lora] =
[
new LocalModelFile
{
- RelativePath = "Lora/mock_model_1.safetensors",
SharedFolderType = SharedFolderType.Lora,
+ RelativePath = "Lora/mock_model_1.safetensors",
},
new LocalModelFile
{
+ SharedFolderType = SharedFolderType.Lora,
RelativePath = "Lora/mock_model_2.safetensors",
+ },
+ new LocalModelFile
+ {
SharedFolderType = SharedFolderType.Lora,
+ RelativePath = "Lora/background_arts.safetensors",
+ PreviewImageFullPath =
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/71c81ddf-d8c3-46b4-843d-9f8f20a9254a/width=512/img.png",
+ ConnectedModelInfo = new ConnectedModelInfo
+ {
+ ModelName = "Background Arts",
+ VersionName = "Anime Style v10",
+ },
},
],
};
diff --git a/StabilityMatrix.Avalonia/DialogHelper.cs b/StabilityMatrix.Avalonia/DialogHelper.cs
index 345a7cf54..0e4dbe524 100644
--- a/StabilityMatrix.Avalonia/DialogHelper.cs
+++ b/StabilityMatrix.Avalonia/DialogHelper.cs
@@ -627,6 +627,9 @@ public sealed class TextBoxField : INotifyPropertyChanged
public int? MinWidth { get; init; }
+ // Is this a password field?
+ public bool IsPassword { get; init; } = false;
+
///
/// Validation action on text changes. Throw exception if invalid.
///
diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
index c50ae9605..d3369988a 100644
--- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
+++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
@@ -27,16 +27,32 @@ namespace StabilityMatrix.Avalonia.Helpers;
public class UnixPrerequisiteHelper(
IDownloadService downloadService,
ISettingsManager settingsManager,
- IPyRunner pyRunner
+ IPyRunner pyRunner,
+ IPyInstallationManager pyInstallationManager
) : IPrerequisiteHelper
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+ private const string UvMacDownloadUrl =
+ "https://github.com/astral-sh/uv/releases/download/0.8.4/uv-aarch64-apple-darwin.tar.gz";
+ private const string UvLinuxDownloadUrl =
+ "https://github.com/astral-sh/uv/releases/download/0.8.4/uv-x86_64-unknown-linux-gnu.tar.gz";
+
private DirectoryPath HomeDir => settingsManager.LibraryDir;
private DirectoryPath AssetsDir => HomeDir.JoinDir("Assets");
- private DirectoryPath PythonDir => AssetsDir.JoinDir("Python310");
- public bool IsPythonInstalled => PythonDir.JoinFile(PyRunner.RelativePythonDllPath).Exists;
+ // Helper method to get Python directory for specific version
+ private DirectoryPath GetPythonDir(PyVersion version) =>
+ version == PyInstallationManager.Python_3_10_11
+ ? AssetsDir.JoinDir("Python310")
+ : AssetsDir.JoinDir($"Python{version.Major}{version.Minor}{version.Micro}");
+
+ // Helper method to check if specific Python version is installed
+ private bool IsPythonVersionInstalled(PyVersion version) =>
+ GetPythonDir(version).JoinFile(PyRunner.RelativePythonDllPath).Exists;
+
+ // Legacy property for compatibility
+ public bool IsPythonInstalled => IsPythonVersionInstalled(PyInstallationManager.DefaultVersion);
private DirectoryPath PortableGitInstallDir => HomeDir + "PortableGit";
public string GitBinPath => PortableGitInstallDir + "bin";
@@ -62,6 +78,26 @@ IPyRunner pyRunner
public bool IsVcBuildToolsInstalled => false;
public bool IsHipSdkInstalled => false;
+ private string UvDownloadPath => Path.Combine(AssetsDir, "uv.tar.gz");
+ private string UvExtractPath => Path.Combine(AssetsDir, "uv");
+ public string UvExePath => Path.Combine(UvExtractPath, "uv");
+ public bool IsUvInstalled => File.Exists(UvExePath);
+ private string ExpectedUvVersion => "0.8.4";
+
+ // Helper method to get Python download URL for a specific version
+ private RemoteResource GetPythonDownloadResource(PyVersion version)
+ {
+ if (version == PyInstallationManager.Python_3_10_11)
+ {
+ return Assets.PythonDownloadUrl;
+ }
+
+ throw new ArgumentException($"Unsupported Python version: {version}", nameof(version));
+ }
+
+ // Helper method to get download path for a specific Python version
+ private string GetPythonDownloadPath(PyVersion version) =>
+ Path.Combine(AssetsDir, $"python-{version}-amd64.tar.gz");
private async Task CheckIsGitInstalled()
{
@@ -70,20 +106,36 @@ private async Task CheckIsGitInstalled()
return isGitInstalled == true;
}
- public Task InstallPackageRequirements(BasePackage package, IProgress? progress = null) =>
- InstallPackageRequirements(package.Prerequisites.ToList(), progress);
+ public Task InstallPackageRequirements(
+ BasePackage package,
+ PyVersion? pyVersion = null,
+ IProgress? progress = null
+ ) => InstallPackageRequirements(package.Prerequisites.ToList(), pyVersion, progress);
public async Task InstallPackageRequirements(
List prerequisites,
+ PyVersion? pyVersion = null,
IProgress? progress = null
)
{
await UnpackResourcesIfNecessary(progress);
+ await InstallUvIfNecessary(progress);
if (prerequisites.Contains(PackagePrerequisite.Python310))
{
- await InstallPythonIfNecessary(progress);
- await InstallVirtualenvIfNecessary(progress);
+ await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);
+ await InstallVirtualenvIfNecessary(PyInstallationManager.Python_3_10_11, progress);
+ }
+
+ if (pyVersion is not null)
+ {
+ if (!await EnsurePythonVersion(pyVersion.Value))
+ {
+ throw new MissingPrerequisiteException(
+ @"Python",
+ @$"Python {pyVersion} was not found and/or failed to install. Please check the logs for more details."
+ );
+ }
}
if (prerequisites.Contains(PackagePrerequisite.Git))
@@ -142,7 +194,8 @@ private async Task InstallVirtualenvIfNecessary(IProgress? progr
public async Task InstallAllIfNecessary(IProgress? progress = null)
{
await UnpackResourcesIfNecessary(progress);
- await InstallPythonIfNecessary(progress);
+ await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);
+ await InstallUvIfNecessary(progress);
}
public async Task UnpackResourcesIfNecessary(IProgress? progress = null)
@@ -215,7 +268,11 @@ private async Task RunGit(ProcessArgs args, string? workingDirectory = null)
{
var command = args.Prepend("git");
- var result = await ProcessRunner.RunBashCommand(command, workingDirectory ?? "");
+ var result = await ProcessRunner.RunBashCommand(
+ command,
+ workingDirectory ?? string.Empty,
+ new Dictionary { { "GIT_TERMINAL_PROMPT", "0" } }
+ );
if (result.ExitCode != 0)
{
Logger.Error(
@@ -235,57 +292,64 @@ private async Task RunGit(ProcessArgs args, string? workingDirectory = null)
public async Task InstallPythonIfNecessary(IProgress? progress = null)
{
- if (IsPythonInstalled)
+ await InstallPythonIfNecessary(PyInstallationManager.DefaultVersion, progress);
+ }
+
+ public async Task InstallPythonIfNecessary(PyVersion version, IProgress? progress = null)
+ {
+ var pythonDir = GetPythonDir(version);
+
+ if (IsPythonVersionInstalled(version))
return;
Directory.CreateDirectory(AssetsDir);
// Download
- var remote = Assets.PythonDownloadUrl;
+ var remote = GetPythonDownloadResource(version);
var url = remote.Url;
var hashSha256 = remote.HashSha256;
var fileName = Path.GetFileName(url.LocalPath);
var downloadPath = Path.Combine(AssetsDir, fileName);
- Logger.Info($"Downloading Python from {url} to {downloadPath}");
+ Logger.Info($"Downloading Python {version} from {url} to {downloadPath}");
try
{
await downloadService.DownloadToFileAsync(url.ToString(), downloadPath, progress);
// Verify hash
var actualHash = await FileHash.GetSha256Async(downloadPath);
- Logger.Info($"Verifying Python hash: (expected: {hashSha256}, actual: {actualHash})");
+ Logger.Info($"Verifying Python {version} hash: (expected: {hashSha256}, actual: {actualHash})");
if (actualHash != hashSha256)
{
throw new Exception(
- $"Python download hash mismatch: expected {hashSha256}, actual {actualHash}"
+ $"Python {version} download hash mismatch: expected {hashSha256}, actual {actualHash}"
);
}
// Extract
- Logger.Info($"Extracting Python Zip: {downloadPath} to {PythonDir}");
- if (PythonDir.Exists)
+ Logger.Info($"Extracting Python {version} Zip: {downloadPath} to {pythonDir}");
+ if (pythonDir.Exists)
{
- await PythonDir.DeleteAsync(true);
+ await pythonDir.DeleteAsync(true);
}
- progress?.Report(new ProgressReport(0, "Installing Python", isIndeterminate: true));
- await ArchiveHelper.Extract7ZAuto(downloadPath, PythonDir);
+ progress?.Report(new ProgressReport(0, $"Installing Python {version}", isIndeterminate: true));
+ await ArchiveHelper.Extract7ZAuto(downloadPath, pythonDir);
// For Unix, move the inner 'python' folder up to the root PythonDir
if (Compat.IsUnix)
{
- var innerPythonDir = PythonDir.JoinDir("python");
+ var innerPythonDir = pythonDir.JoinDir("python");
if (!innerPythonDir.Exists)
{
throw new Exception(
- $"Python download did not contain expected inner 'python' folder: {innerPythonDir}"
+ $"Python {version} download did not contain expected inner 'python' folder: {innerPythonDir}"
);
}
foreach (var folder in Directory.EnumerateDirectories(innerPythonDir))
{
var folderName = Path.GetFileName(folder);
- var dest = Path.Combine(PythonDir, folderName);
+ var dest = Path.Combine(pythonDir, folderName);
Directory.Move(folder, dest);
}
Directory.Delete(innerPythonDir);
@@ -302,9 +366,10 @@ public async Task InstallPythonIfNecessary(IProgress? progress =
// Initialize pyrunner and install virtualenv
await pyRunner.Initialize();
- await pyRunner.InstallPackage("virtualenv");
+ await pyRunner.SwitchToInstallation(version);
+ await pyRunner.InstallPackage("virtualenv", version);
- progress?.Report(new ProgressReport(1, "Installing Python", isIndeterminate: false));
+ progress?.Report(new ProgressReport(1, $"Installing Python {version}", isIndeterminate: false));
}
public Task GetGitOutput(ProcessArgs args, string? workingDirectory = null)
@@ -312,6 +377,21 @@ public Task GetGitOutput(ProcessArgs args, string? workingDirecto
return ProcessRunner.RunBashCommand(args.Prepend("git"), workingDirectory ?? "");
}
+ private async Task RunNode(
+ ProcessArgs args,
+ string? workingDirectory = null,
+ IReadOnlyDictionary? envVars = null
+ )
+ {
+ var nodePath = Path.Combine(NodeDir, "bin", "node");
+ var result = await ProcessRunner
+ .GetProcessResultAsync(nodePath, args, workingDirectory, envVars)
+ .ConfigureAwait(false);
+
+ result.EnsureSuccessExitCode();
+ return result.StandardOutput ?? result.StandardError ?? string.Empty;
+ }
+
[Localizable(false)]
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("macOS")]
@@ -351,20 +431,20 @@ public async Task RunNpm(
);
}
- // NOTE TO FUTURE DEVS: if this is causing merge conflicts with dev, just nuke it we don't need anymore
- private async Task RunNode(
+ public AnsiProcess RunNpmDetached(
ProcessArgs args,
string? workingDirectory = null,
+ Action? onProcessOutput = null,
IReadOnlyDictionary? envVars = null
)
{
- var nodePath = Path.Combine(NodeDir, "bin", "node");
- var result = await ProcessRunner
- .GetProcessResultAsync(nodePath, args, workingDirectory, envVars)
- .ConfigureAwait(false);
-
- result.EnsureSuccessExitCode();
- return result.StandardOutput ?? result.StandardError ?? string.Empty;
+ return ProcessRunner.StartAnsiProcess(
+ NpmPath,
+ args,
+ workingDirectory,
+ onProcessOutput,
+ envVars ?? new Dictionary()
+ );
}
[SupportedOSPlatform("Linux")]
@@ -467,6 +547,155 @@ public async Task InstallNodeIfNecessary(IProgress? progress = n
File.Delete(nodeDownloadPath);
}
+ [SupportedOSPlatform("Linux")]
+ [SupportedOSPlatform("macOS")]
+ public async Task InstallVirtualenvIfNecessary(
+ PyVersion version,
+ IProgress? progress = null
+ )
+ {
+ // Check if pip and venv are installed for this version
+ var pipInstalled = File.Exists(Path.Combine(GetPythonDir(version), "bin", "pip3"));
+ var venvInstalled = Directory.Exists(
+ Path.Combine(GetPythonDir(version), "Scripts", "virtualenv" + Compat.ExeExtension)
+ );
+
+ if (!pipInstalled || !venvInstalled)
+ {
+ progress?.Report(
+ new ProgressReport(
+ -1f,
+ $"Installing Python {version} prerequisites...",
+ isIndeterminate: true
+ )
+ );
+
+ await pyRunner.Initialize();
+ await pyRunner.SwitchToInstallation(version);
+
+ if (!pipInstalled)
+ {
+ await pyRunner.SetupPip(version).ConfigureAwait(false);
+ }
+
+ if (!venvInstalled)
+ {
+ await pyRunner.InstallPackage("virtualenv", version).ConfigureAwait(false);
+ }
+ }
+ }
+
+ public async Task InstallUvIfNecessary(IProgress? progress = null)
+ {
+ if (IsUvInstalled)
+ {
+ var version = await GetInstalledUvVersionAsync();
+ if (version.Contains(ExpectedUvVersion))
+ {
+ Logger.Debug("UV already installed at {UvExePath}", UvExePath);
+ return;
+ }
+
+ Logger.Warn(
+ "UV version mismatch at {UvExePath}. Expected: {ExpectedVersion}, Found: {FoundVersion}",
+ UvExePath,
+ ExpectedUvVersion,
+ version
+ );
+ }
+
+ Logger.Info("UV not found at {UvExePath}, downloading...", UvExePath);
+
+ Directory.CreateDirectory(AssetsDir);
+
+ var downloadUrl = Compat.IsMacOS ? UvMacDownloadUrl : UvLinuxDownloadUrl;
+
+ // Download UV archive
+ await downloadService.DownloadToFileAsync(downloadUrl, UvDownloadPath, progress: progress);
+
+ progress?.Report(
+ new ProgressReport(
+ progress: 0.5f,
+ isIndeterminate: true,
+ type: ProgressType.Generic,
+ message: "Installing UV package manager..."
+ )
+ );
+
+ // Create extraction directory
+ Directory.CreateDirectory(UvExtractPath);
+
+ // Extract UV
+ await ArchiveHelper.Extract7ZTar(UvDownloadPath, UvExtractPath);
+
+ // On Mac/Linux, the extraction might create a platform-specific folder
+ // (e.g., uv-aarch64-apple-darwin or uv-x86_64-unknown-linux-gnu)
+ // We need to move both the uv and uvx executables from that folder to the expected location
+
+ // Find platform-specific directory
+ var platformSpecificDir = Directory
+ .GetDirectories(UvExtractPath)
+ .FirstOrDefault(dir => Path.GetFileName(dir).StartsWith("uv-"));
+
+ if (platformSpecificDir != null)
+ {
+ Logger.Debug("Found platform-specific UV directory: {PlatformDir}", platformSpecificDir);
+
+ // List of files to move: uv and uvx
+ var filesToMove = new Dictionary
+ {
+ { Path.Combine(platformSpecificDir, "uv"), Path.Combine(UvExtractPath, "uv") },
+ { Path.Combine(platformSpecificDir, "uvx"), Path.Combine(UvExtractPath, "uvx") },
+ };
+
+ var anyFilesMoved = false;
+
+ // Move each file if it exists
+ foreach (var entry in filesToMove)
+ {
+ var sourcePath = entry.Key;
+ var destPath = entry.Value;
+
+ if (File.Exists(sourcePath))
+ {
+ Logger.Debug("Moving file from {Source} to {Destination}", sourcePath, destPath);
+
+ // Ensure the destination doesn't exist before moving
+ if (File.Exists(destPath))
+ {
+ File.Delete(destPath);
+ }
+
+ File.Move(sourcePath, destPath);
+ anyFilesMoved = true;
+
+ // Make the executable file executable
+ var process = ProcessRunner.StartAnsiProcess("chmod", ["+x", destPath]);
+ await process.WaitForExitAsync();
+ }
+ }
+
+ // Delete the now-empty platform directory after moving all files
+ if (anyFilesMoved)
+ {
+ Directory.Delete(platformSpecificDir, true);
+ }
+ }
+ else if (File.Exists(UvExePath))
+ {
+ // For Windows or if we already have the file in the right place, just make it executable
+ var process = ProcessRunner.StartAnsiProcess("chmod", ["+x", UvExePath]);
+ await process.WaitForExitAsync();
+ }
+
+ progress?.Report(
+ new ProgressReport(progress: 1f, message: "UV installation complete", type: ProgressType.Generic)
+ );
+
+ // Clean up download
+ File.Delete(UvDownloadPath);
+ }
+
private async Task DownloadAndExtractPrerequisite(
IProgress? progress,
string downloadUrl,
@@ -501,6 +730,26 @@ string extractPath
File.Delete(downloadPath);
}
+ private async Task GetInstalledUvVersionAsync()
+ {
+ try
+ {
+ var processResult = await ProcessRunner.GetProcessResultAsync(UvExePath, "--version");
+ return processResult.StandardOutput ?? processResult.StandardError ?? string.Empty;
+ }
+ catch (Exception e)
+ {
+ Logger.Warn(e, "Failed to get UV version from {UvExePath}", UvExePath);
+ return string.Empty;
+ }
+ }
+
+ private async Task EnsurePythonVersion(PyVersion pyVersion)
+ {
+ var result = await pyInstallationManager.GetInstallationAsync(pyVersion);
+ return result.Exists();
+ }
+
[UnsupportedOSPlatform("Linux")]
[UnsupportedOSPlatform("macOS")]
public Task InstallTkinterIfNecessary(IProgress? progress = null)
@@ -526,9 +775,17 @@ public Task FixGitLongPaths()
[UnsupportedOSPlatform("macOS")]
public Task AddMissingLibsToVenv(
DirectoryPath installedPackagePath,
+ PyBaseInstall baseInstall,
IProgress? progress = null
)
{
throw new PlatformNotSupportedException();
}
+
+ [UnsupportedOSPlatform("Linux")]
+ [UnsupportedOSPlatform("macOS")]
+ public Task InstallTkinterIfNecessary(PyVersion version, IProgress? progress = null)
+ {
+ throw new PlatformNotSupportedException();
+ }
}
diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
index eb2afb259..77e5e1ec0 100644
--- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
+++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
@@ -2,6 +2,7 @@
using System.Runtime.Versioning;
using Microsoft.Win32;
using NLog;
+using Python.Runtime;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo;
@@ -19,7 +20,8 @@ namespace StabilityMatrix.Avalonia.Helpers;
public class WindowsPrerequisiteHelper(
IDownloadService downloadService,
ISettingsManager settingsManager,
- IPyRunner pyRunner
+ IPyRunner pyRunner,
+ IPyInstallationManager pyInstallationManager
) : IPrerequisiteHelper
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -46,6 +48,9 @@ IPyRunner pyRunner
"https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-24.Q4-Win10-Win11-For-HIP.exe";
private const string PythonLibsDownloadUrl = "https://cdn.lykos.ai/python_libs_for_sage.zip";
+ private const string UvWindowsDownloadUrl =
+ "https://github.com/astral-sh/uv/releases/download/0.8.4/uv-x86_64-pc-windows-msvc.zip";
+
private string HomeDir => settingsManager.LibraryDir;
private string VcRedistDownloadPath => Path.Combine(HomeDir, "vcredist.x64.exe");
@@ -53,21 +58,36 @@ IPyRunner pyRunner
private string AssetsDir => Path.Combine(HomeDir, "Assets");
private string SevenZipPath => Path.Combine(AssetsDir, "7za.exe");
- private string PythonDownloadPath => Path.Combine(AssetsDir, "python-3.10.11-embed-amd64.zip");
- private string PythonDir => Path.Combine(AssetsDir, "Python310");
- private string PythonDllPath => Path.Combine(PythonDir, "python310.dll");
- private string PythonLibraryZipPath => Path.Combine(PythonDir, "python310.zip");
- private string GetPipPath => Path.Combine(PythonDir, "get-pip.pyc");
+ private string GetPythonDownloadPath(PyVersion version) =>
+ Path.Combine(
+ AssetsDir,
+ version == PyInstallationManager.Python_3_10_11
+ ? "python-3.10.11-embed-amd64.zip"
+ : $"python-{version}-amd64.tar.gz"
+ );
+
+ private string GetPythonDir(PyVersion version) =>
+ version == PyInstallationManager.Python_3_10_11
+ ? Path.Combine(AssetsDir, "Python310")
+ : Path.Combine(AssetsDir, $"Python{version.Major}{version.Minor}{version.Micro}");
+
+ private string GetPythonDllPath(PyVersion version) =>
+ Path.Combine(GetPythonDir(version), $"python{version.Major}{version.Minor}.dll");
+
+ private string GetPythonLibraryZipPath(PyVersion version) =>
+ Path.Combine(GetPythonDir(version), $"python{version.Major}{version.Minor}.zip");
+
+ private string GetPipPath(PyVersion version) => Path.Combine(GetPythonDir(version), "get-pip.pyc");
// Temporary directory to extract venv to during python install
- private string VenvTempDir => Path.Combine(PythonDir, "venv");
+ private string GetVenvTempDir(PyVersion version) => Path.Combine(GetPythonDir(version), "venv");
private string PortableGitInstallDir => Path.Combine(HomeDir, "PortableGit");
private string PortableGitDownloadPath => Path.Combine(HomeDir, "PortableGit.7z.exe");
private string GitExePath => Path.Combine(PortableGitInstallDir, "bin", "git.exe");
private string TkinterZipPath => Path.Combine(AssetsDir, "tkinter.zip");
- private string TkinterExtractPath => PythonDir;
- private string TkinterExistsPath => Path.Combine(PythonDir, "tkinter");
+ private string TkinterExtractPath => Path.Combine(AssetsDir, "Python310");
+ private string TkinterExistsPath => Path.Combine(TkinterExtractPath, "tkinter");
private string NodeExistsPath => Path.Combine(AssetsDir, "nodejs", "npm.cmd");
private string NodeExePath => Path.Combine(AssetsDir, "nodejs", "node.exe");
private string NodeDownloadPath => Path.Combine(AssetsDir, "nodejs.zip");
@@ -92,11 +112,34 @@ IPyRunner pyRunner
private string HipInstalledPath =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "AMD", "ROCm", "6.2");
+ private string UvDownloadPath => Path.Combine(AssetsDir, "uv.zip");
+ private string UvExtractPath => Path.Combine(AssetsDir, "uv");
+ public string UvExePath => Path.Combine(UvExtractPath, "uv.exe");
+ public bool IsUvInstalled => File.Exists(UvExePath);
+ private string ExpectedUvVersion => "0.8.4";
+
public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin");
- public bool IsPythonInstalled => File.Exists(PythonDllPath);
public bool IsVcBuildToolsInstalled => Directory.Exists(VcBuildToolsExistsPath);
public bool IsHipSdkInstalled => Directory.Exists(HipInstalledPath);
+ // Check if a specific Python version is installed
+ public bool IsPythonVersionInstalled(PyVersion version) => File.Exists(GetPythonDllPath(version));
+
+ // Legacy property for compatibility
+ public bool IsPythonInstalled => IsPythonVersionInstalled(PyInstallationManager.DefaultVersion);
+
+ // Implement interface method - uses default Python version
+ public Task InstallPythonIfNecessary(IProgress? progress = null)
+ {
+ return InstallPythonIfNecessary(PyInstallationManager.DefaultVersion, progress);
+ }
+
+ // Implement interface method - uses default Python version
+ public Task InstallTkinterIfNecessary(IProgress? progress = null)
+ {
+ return InstallTkinterIfNecessary(PyInstallationManager.DefaultVersion, progress);
+ }
+
public async Task RunGit(
ProcessArgs args,
Action? onProcessOutput = null,
@@ -111,6 +154,7 @@ public async Task RunGit(
environmentVariables: new Dictionary
{
{ "PATH", Compat.GetEnvPathWithExtensions(GitBinPath) },
+ { "GIT_TERMINAL_PROMPT", "0" },
}
);
await process.WaitForExitAsync().ConfigureAwait(false);
@@ -133,6 +177,20 @@ public Task GetGitOutput(ProcessArgs args, string? workingDirecto
);
}
+ private async Task RunNode(
+ ProcessArgs args,
+ string? workingDirectory = null,
+ IReadOnlyDictionary? envVars = null
+ )
+ {
+ var result = await ProcessRunner
+ .GetProcessResultAsync(NodeExePath, args, workingDirectory, envVars)
+ .ConfigureAwait(false);
+
+ result.EnsureSuccessExitCode();
+ return result.StandardOutput ?? result.StandardError ?? string.Empty;
+ }
+
public async Task RunNpm(
ProcessArgs args,
string? workingDirectory = null,
@@ -149,30 +207,85 @@ public async Task RunNpm(
onProcessOutput?.Invoke(ProcessOutput.FromStdErrLine(result.StandardError));
}
- // NOTE TO FUTURE DEVS: if this is causing merge conflicts with dev, just nuke it we don't need anymore
- private async Task RunNode(
+ public AnsiProcess RunNpmDetached(
ProcessArgs args,
string? workingDirectory = null,
+ Action? onProcessOutput = null,
IReadOnlyDictionary? envVars = null
)
{
- var result = await ProcessRunner
- .GetProcessResultAsync(NodeExePath, args, workingDirectory, envVars)
- .ConfigureAwait(false);
-
- result.EnsureSuccessExitCode();
- return result.StandardOutput ?? result.StandardError ?? string.Empty;
+ return ProcessRunner.StartAnsiProcess(
+ NodeExistsPath,
+ args,
+ workingDirectory,
+ onProcessOutput,
+ envVars ?? new Dictionary()
+ );
}
- public Task InstallPackageRequirements(BasePackage package, IProgress? progress = null) =>
- InstallPackageRequirements(package.Prerequisites.ToList(), progress);
+ public Task InstallPackageRequirements(
+ BasePackage package,
+ PyVersion? pyVersion = null,
+ IProgress? progress = null
+ ) => InstallPackageRequirements(package.Prerequisites.ToList(), pyVersion, progress);
+
+ public async Task InstallUvIfNecessary(IProgress? progress = null)
+ {
+ if (IsUvInstalled)
+ {
+ var version = await GetInstalledUvVersionAsync();
+ if (version.Contains(ExpectedUvVersion))
+ {
+ Logger.Debug("UV already installed at {UvExePath}", UvExePath);
+ return;
+ }
+
+ Logger.Warn(
+ "UV version mismatch at {UvExePath}. Expected: {ExpectedVersion}, Found: {FoundVersion}",
+ UvExePath,
+ ExpectedUvVersion,
+ version
+ );
+ }
+
+ Logger.Info("UV not found at {UvExePath}, downloading...", UvExePath);
+
+ Directory.CreateDirectory(AssetsDir);
+
+ // Download UV zip
+ await downloadService.DownloadToFileAsync(UvWindowsDownloadUrl, UvDownloadPath, progress: progress);
+
+ progress?.Report(
+ new ProgressReport(
+ progress: 0.5f,
+ isIndeterminate: true,
+ type: ProgressType.Generic,
+ message: "Installing UV package manager..."
+ )
+ );
+
+ // Create extraction directory
+ Directory.CreateDirectory(UvExtractPath);
+
+ // Extract UV
+ await ArchiveHelper.Extract(UvDownloadPath, UvExtractPath, progress);
+
+ progress?.Report(
+ new ProgressReport(progress: 1f, message: "UV installation complete", type: ProgressType.Generic)
+ );
+
+ // Clean up download
+ File.Delete(UvDownloadPath);
+ }
public async Task InstallPackageRequirements(
List prerequisites,
+ PyVersion? pyVersion = null,
IProgress? progress = null
)
{
await UnpackResourcesIfNecessary(progress);
+ await InstallUvIfNecessary(progress);
if (prerequisites.Contains(PackagePrerequisite.HipSdk))
{
@@ -181,8 +294,19 @@ public async Task InstallPackageRequirements(
if (prerequisites.Contains(PackagePrerequisite.Python310))
{
- await InstallPythonIfNecessary(progress);
- await InstallVirtualenvIfNecessary(progress);
+ await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);
+ await InstallVirtualenvIfNecessary(PyInstallationManager.Python_3_10_11, progress);
+ }
+
+ if (pyVersion is not null)
+ {
+ if (!await EnsurePythonVersion(pyVersion.Value))
+ {
+ throw new MissingPrerequisiteException(
+ @"Python",
+ @$"Python {pyVersion} was not found and/or failed to install. Please check the logs for more details."
+ );
+ }
}
if (prerequisites.Contains(PackagePrerequisite.Git))
@@ -207,24 +331,25 @@ public async Task InstallPackageRequirements(
if (prerequisites.Contains(PackagePrerequisite.Tkinter))
{
- await InstallTkinterIfNecessary(progress);
+ await InstallTkinterIfNecessary(PyInstallationManager.Python_3_10_11, progress);
}
- if (prerequisites.Contains(PackagePrerequisite.VcBuildTools))
- {
- await InstallVcBuildToolsIfNecessary(progress);
- }
+ // if (prerequisites.Contains(PackagePrerequisite.VcBuildTools))
+ // {
+ // await InstallVcBuildToolsIfNecessary(progress);
+ // }
}
public async Task InstallAllIfNecessary(IProgress? progress = null)
{
await InstallVcRedistIfNecessary(progress);
await UnpackResourcesIfNecessary(progress);
- await InstallPythonIfNecessary(progress);
+ await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);
await InstallGitIfNecessary(progress);
await InstallNodeIfNecessary(progress);
await InstallVcBuildToolsIfNecessary(progress);
await InstallHipSdkIfNecessary(progress);
+ await InstallUvIfNecessary(progress);
}
public async Task UnpackResourcesIfNecessary(IProgress? progress = null)
@@ -243,49 +368,69 @@ public async Task UnpackResourcesIfNecessary(IProgress? progress
progress?.Report(new ProgressReport(1, message: "Unpacking resources", isIndeterminate: false));
}
- public async Task InstallPythonIfNecessary(IProgress? progress = null)
+ public async Task InstallPythonIfNecessary(PyVersion version, IProgress? progress = null)
{
- if (File.Exists(PythonDllPath))
+ var pythonDllPath = GetPythonDllPath(version);
+
+ if (File.Exists(pythonDllPath))
{
- Logger.Debug("Python already installed at {PythonDllPath}", PythonDllPath);
+ Logger.Debug("Python {Version} already installed at {PythonDllPath}", version, pythonDllPath);
return;
}
- Logger.Info("Python not found at {PythonDllPath}, downloading...", PythonDllPath);
+ Logger.Info("Python {Version} not found at {PythonDllPath}, downloading...", version, pythonDllPath);
Directory.CreateDirectory(AssetsDir);
+ var pythonLibraryZipPath = GetPythonLibraryZipPath(version);
+
// Delete existing python zip if it exists
- if (File.Exists(PythonLibraryZipPath))
+ if (File.Exists(pythonLibraryZipPath))
+ {
+ File.Delete(pythonLibraryZipPath);
+ }
+
+ // Get the correct download URL for this Python version
+ RemoteResource? remote = null;
+ if (version == PyInstallationManager.Python_3_10_11)
+ {
+ remote = Assets.PythonDownloadUrl;
+ }
+ else
{
- File.Delete(PythonLibraryZipPath);
+ throw new ArgumentException($"No download URL configured for Python {version}");
}
- var remote = Assets.PythonDownloadUrl;
- var url = remote.Url.ToString();
- Logger.Info($"Downloading Python from {url} to {PythonLibraryZipPath}");
+ var url = remote.Value.Url.ToString();
+ var pythonDownloadPath = GetPythonDownloadPath(version);
+
+ Logger.Info($"Downloading Python {version} from {url} to {pythonDownloadPath}");
// Cleanup to remove zip if download fails
try
{
// Download python zip
- await downloadService.DownloadToFileAsync(url, PythonDownloadPath, progress: progress);
+ await downloadService.DownloadToFileAsync(url, pythonDownloadPath, progress: progress);
// Verify python hash
- var downloadHash = await FileHash.GetSha256Async(PythonDownloadPath, progress);
- if (downloadHash != remote.HashSha256)
+ var downloadHash = await FileHash.GetSha256Async(pythonDownloadPath, progress);
+ if (downloadHash != remote.Value.HashSha256)
{
- var fileExists = File.Exists(PythonDownloadPath);
- var fileSize = new FileInfo(PythonDownloadPath).Length;
+ var fileExists = File.Exists(pythonDownloadPath);
+ var fileSize = new FileInfo(pythonDownloadPath).Length;
var msg =
- $"Python download hash mismatch: {downloadHash} != {remote.HashSha256} "
+ $"Python download hash mismatch: {downloadHash} != {remote.Value.HashSha256} "
+ $"(file exists: {fileExists}, size: {fileSize})";
throw new Exception(msg);
}
- progress?.Report(new ProgressReport(progress: 1f, message: "Python download complete"));
+ progress?.Report(
+ new ProgressReport(progress: 1f, message: $"Python {version} download complete")
+ );
- progress?.Report(new ProgressReport(-1, "Installing Python...", isIndeterminate: true));
+ progress?.Report(
+ new ProgressReport(-1, $"Installing Python {version}...", isIndeterminate: true)
+ );
// We also need 7z if it's not already unpacked
if (!File.Exists(SevenZipPath))
@@ -294,97 +439,149 @@ public async Task InstallPythonIfNecessary(IProgress? progress =
await Assets.SevenZipLicense.ExtractToDir(AssetsDir);
}
+ var pythonDir = GetPythonDir(version);
+
// Delete existing python dir
- if (Directory.Exists(PythonDir))
+ if (Directory.Exists(pythonDir))
{
- Directory.Delete(PythonDir, true);
+ Directory.Delete(pythonDir, true);
}
- // Unzip python
- await ArchiveHelper.Extract7Z(PythonDownloadPath, PythonDir);
-
- try
+ // For Python 3.10.11, we need to handle embedded venv folder
+ if (version == PyInstallationManager.Python_3_10_11)
{
- // Extract embedded venv folder
- Directory.CreateDirectory(VenvTempDir);
- foreach (var (resource, relativePath) in Assets.PyModuleVenv)
+ try
{
- var path = Path.Combine(VenvTempDir, relativePath);
- // Create missing directories
- var dir = Path.GetDirectoryName(path);
- if (dir != null)
+ // Extract embedded venv folder
+ var venvTempDir = GetVenvTempDir(version);
+ Directory.CreateDirectory(venvTempDir);
+ foreach (var (resource, relativePath) in Assets.PyModuleVenv)
{
- Directory.CreateDirectory(dir);
+ var path = Path.Combine(venvTempDir, relativePath);
+ // Create missing directories
+ var dir = Path.GetDirectoryName(path);
+ if (dir != null)
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ await resource.ExtractTo(path);
}
-
- await resource.ExtractTo(path);
+ // Add venv to python's library zip
+ await ArchiveHelper.AddToArchive7Z(pythonLibraryZipPath, venvTempDir);
}
- // Add venv to python's library zip
+ finally
+ {
+ // Remove venv
+ var venvTempDir = GetVenvTempDir(version);
+ if (Directory.Exists(venvTempDir))
+ {
+ Directory.Delete(venvTempDir, true);
+ }
+ }
+ }
- await ArchiveHelper.AddToArchive7Z(PythonLibraryZipPath, VenvTempDir);
+ // Unzip python
+ if (version == PyInstallationManager.Python_3_10_11)
+ {
+ await ArchiveHelper.Extract7Z(pythonDownloadPath, pythonDir);
+ // We need to uncomment the #import site line in python._pth for pip to work
+ var pythonPthPath = Path.Combine(pythonDir, $"python{version.Major}{version.Minor}._pth");
+ var pythonPthContent = await File.ReadAllTextAsync(pythonPthPath);
+ pythonPthContent = pythonPthContent.Replace("#import site", "import site");
+ await File.WriteAllTextAsync(pythonPthPath, pythonPthContent);
+
+ // Install TKinter
+ await InstallTkinterIfNecessary(version, progress);
}
- finally
+ else
{
- // Remove venv
- if (Directory.Exists(VenvTempDir))
+ await ArchiveHelper.Extract7ZTar(pythonDownloadPath, pythonDir);
+ // it gets extracted into a folder named `python`, we need to move the contents to this pythonDir
+ var extractedDir = Path.Combine(pythonDir, "python");
+ if (Directory.Exists(extractedDir))
{
- Directory.Delete(VenvTempDir, true);
+ foreach (var file in Directory.GetFiles(extractedDir))
+ {
+ var destFile = Path.Combine(pythonDir, Path.GetFileName(file));
+ File.Move(file, destFile);
+ }
+
+ foreach (var dir in Directory.GetDirectories(extractedDir))
+ {
+ var destDir = Path.Combine(pythonDir, Path.GetFileName(dir));
+ Directory.Move(dir, destDir);
+ }
+
+ Directory.Delete(extractedDir, true);
}
}
// Extract get-pip.pyc
- await Assets.PyScriptGetPip.ExtractToDir(PythonDir);
-
- // We need to uncomment the #import site line in python310._pth for pip to work
- var pythonPthPath = Path.Combine(PythonDir, "python310._pth");
- var pythonPthContent = await File.ReadAllTextAsync(pythonPthPath);
- pythonPthContent = pythonPthContent.Replace("#import site", "import site");
- await File.WriteAllTextAsync(pythonPthPath, pythonPthContent);
-
- // Install TKinter
- await InstallTkinterIfNecessary(progress);
-
- progress?.Report(new ProgressReport(1f, "Python install complete"));
+ await Assets.PyScriptGetPip.ExtractToDir(pythonDir);
+ progress?.Report(new ProgressReport(1f, $"Python {version} install complete"));
}
finally
{
// Always delete zip after download
- if (File.Exists(PythonDownloadPath))
+ if (File.Exists(pythonDownloadPath))
{
- File.Delete(PythonDownloadPath);
+ File.Delete(pythonDownloadPath);
}
}
}
- private async Task InstallVirtualenvIfNecessary(IProgress? progress = null)
+ public async Task InstallVirtualenvIfNecessary(
+ PyVersion version,
+ IProgress? progress = null
+ )
{
- // python stuff
- if (!PyRunner.PipInstalled || !PyRunner.VenvInstalled)
+ var installation = await pyInstallationManager.GetInstallationAsync(version);
+
+ // Check if pip and venv are installed for this version
+ if (!installation.PipInstalled || !installation.VenvInstalled)
{
progress?.Report(
- new ProgressReport(-1f, "Installing Python prerequisites...", isIndeterminate: true)
+ new ProgressReport(
+ -1f,
+ $"Installing Python {version} prerequisites...",
+ isIndeterminate: true
+ )
);
- await pyRunner.Initialize().ConfigureAwait(false);
+ // Switch to this version if needed
+ if (PythonEngine.IsInitialized)
+ {
+ await pyRunner.SwitchToInstallation(version).ConfigureAwait(false);
+ }
+ else
+ {
+ // Initialize with this version
+ await pyRunner.Initialize().ConfigureAwait(false);
+ await pyRunner.SwitchToInstallation(version).ConfigureAwait(false);
+ }
- if (!PyRunner.PipInstalled)
+ if (!installation.PipInstalled)
{
- await pyRunner.SetupPip().ConfigureAwait(false);
+ await pyRunner.SetupPip(version).ConfigureAwait(false);
}
- if (!PyRunner.VenvInstalled)
+ if (!installation.VenvInstalled)
{
- await pyRunner.InstallPackage("virtualenv").ConfigureAwait(false);
+ await pyRunner.InstallPackage("virtualenv", version).ConfigureAwait(false);
}
}
}
[SupportedOSPlatform("windows")]
- public async Task InstallTkinterIfNecessary(IProgress? progress = null)
+ public async Task InstallTkinterIfNecessary(PyVersion version, IProgress? progress = null)
{
- if (!Directory.Exists(TkinterExistsPath))
+ var pythonDir = GetPythonDir(version);
+ var tkinterPath = Path.Combine(pythonDir, "tkinter");
+
+ if (!Directory.Exists(tkinterPath))
{
- Logger.Info("Downloading Tkinter");
+ Logger.Info($"Downloading Tkinter for Python {version}");
await downloadService.DownloadToFileAsync(TkinterDownloadUrl, TkinterZipPath, progress: progress);
progress?.Report(
new ProgressReport(
@@ -394,7 +591,7 @@ public async Task InstallTkinterIfNecessary(IProgress? progress
)
);
- await ArchiveHelper.Extract(TkinterZipPath, TkinterExtractPath, progress);
+ await ArchiveHelper.Extract(TkinterZipPath, pythonDir, progress);
File.Delete(TkinterZipPath);
}
@@ -678,38 +875,50 @@ public async Task RunDotnet(
[SupportedOSPlatform("Windows")]
public async Task AddMissingLibsToVenv(
DirectoryPath installedPackagePath,
+ PyBaseInstall baseInstall,
IProgress? progress = null
)
{
var venvLibsDir = installedPackagePath.JoinDir("venv", "libs");
var venvIncludeDir = installedPackagePath.JoinDir("venv", "include");
- if (
- venvLibsDir.Exists
- && venvIncludeDir.Exists
- && venvLibsDir.JoinFile("python3.lib").Exists
- && venvLibsDir.JoinFile("python310.lib").Exists
- )
+ if (venvLibsDir.Exists && venvIncludeDir.Exists && venvLibsDir.JoinFile("python3.lib").Exists)
{
Logger.Debug("Python libs already installed at {VenvLibsDir}", venvLibsDir);
return;
}
- var downloadPath = installedPackagePath.JoinFile("python_libs_for_sage.zip");
- var venvDir = installedPackagePath.JoinDir("venv");
- await downloadService
- .DownloadToFileAsync(PythonLibsDownloadUrl, downloadPath, progress)
- .ConfigureAwait(false);
-
- progress?.Report(
- new ProgressReport(-1f, message: "Extracting Python libraries", isIndeterminate: true)
- );
- await ArchiveHelper.Extract7Z(downloadPath, venvDir, progress);
-
- var includeFolder = venvDir.JoinDir("include");
- var scriptsIncludeFolder = venvDir.JoinDir("Scripts").JoinDir("include");
- await includeFolder.CopyToAsync(scriptsIncludeFolder);
-
- await downloadPath.DeleteAsync();
+ var sourceLibsDir = new DirectoryPath(baseInstall.RootPath, "libs");
+ var sourceIncludeDir = new DirectoryPath(baseInstall.RootPath, "include");
+
+ var destLibsDir = installedPackagePath.JoinDir("venv", "libs");
+ var destIncludeDir = installedPackagePath.JoinDir("venv", "include");
+ var destIncludeScriptsDir = installedPackagePath.JoinDir("venv", "Scripts", "include");
+
+ destLibsDir.Create();
+ destIncludeDir.Create();
+ destIncludeScriptsDir.Create();
+
+ // Copy libs
+ await sourceLibsDir.CopyToAsync(destLibsDir);
+ await sourceIncludeDir.CopyToAsync(destIncludeDir);
+ await sourceIncludeDir.CopyToAsync(destIncludeScriptsDir);
+
+ // var downloadPath = installedPackagePath.JoinFile("python_libs_for_sage.zip");
+ // var venvDir = installedPackagePath.JoinDir("venv");
+ // await downloadService
+ // .DownloadToFileAsync(PythonLibsDownloadUrl, downloadPath, progress)
+ // .ConfigureAwait(false);
+ //
+ // progress?.Report(
+ // new ProgressReport(-1f, message: "Extracting Python libraries", isIndeterminate: true)
+ // );
+ // await ArchiveHelper.Extract7Z(downloadPath, venvDir, progress);
+ //
+ // var includeFolder = venvDir.JoinDir("include");
+ // var scriptsIncludeFolder = venvDir.JoinDir("Scripts").JoinDir("include");
+ // await includeFolder.CopyToAsync(scriptsIncludeFolder);
+ //
+ // await downloadPath.DeleteAsync();
}
private async Task DownloadAndExtractPrerequisite(
@@ -894,4 +1103,24 @@ _ when downloadUrl.Contains("gfx1010") =>
return null;
}
+
+ private async Task GetInstalledUvVersionAsync()
+ {
+ try
+ {
+ var processResult = await ProcessRunner.GetProcessResultAsync(UvExePath, "--version");
+ return processResult.StandardOutput ?? processResult.StandardError ?? string.Empty;
+ }
+ catch (Exception e)
+ {
+ Logger.Warn(e, "Failed to get UV version from {UvExePath}", UvExePath);
+ return string.Empty;
+ }
+ }
+
+ private async Task EnsurePythonVersion(PyVersion pyVersion)
+ {
+ var result = await pyInstallationManager.GetInstallationAsync(pyVersion);
+ return result.Exists();
+ }
}
diff --git a/StabilityMatrix.Avalonia/Languages/Cultures.cs b/StabilityMatrix.Avalonia/Languages/Cultures.cs
index 3eeea37b8..69c7150d1 100644
--- a/StabilityMatrix.Avalonia/Languages/Cultures.cs
+++ b/StabilityMatrix.Avalonia/Languages/Cultures.cs
@@ -18,23 +18,24 @@ public static class Cultures
public static NumberFormatInfo CurrentNumberFormat => Thread.CurrentThread.CurrentCulture.NumberFormat;
- public static readonly Dictionary SupportedCulturesByCode =
- new()
- {
- ["en-US"] = Default,
- ["ja-JP"] = new CultureInfo("ja-JP"),
- ["zh-Hans"] = new CultureInfo("zh-Hans"),
- ["zh-Hant"] = new CultureInfo("zh-Hant"),
- ["it-IT"] = new CultureInfo("it-IT"),
- ["fr-FR"] = new CultureInfo("fr-FR"),
- ["es"] = new CultureInfo("es"),
- ["ru-RU"] = new CultureInfo("ru-RU"),
- ["tr-TR"] = new CultureInfo("tr-TR"),
- ["de"] = new CultureInfo("de"),
- ["pt-PT"] = new CultureInfo("pt-PT"),
- ["pt-BR"] = new CultureInfo("pt-BR"),
- ["ko-KR"] = new CultureInfo("ko-KR")
- };
+ public static readonly Dictionary SupportedCulturesByCode = new()
+ {
+ ["en-US"] = Default,
+ ["ja-JP"] = new CultureInfo("ja-JP"),
+ ["zh-Hans"] = new CultureInfo("zh-Hans"),
+ ["zh-Hant"] = new CultureInfo("zh-Hant"),
+ ["it-IT"] = new CultureInfo("it-IT"),
+ ["fr-FR"] = new CultureInfo("fr-FR"),
+ ["es"] = new CultureInfo("es"),
+ ["ru-RU"] = new CultureInfo("ru-RU"),
+ ["tr-TR"] = new CultureInfo("tr-TR"),
+ ["de"] = new CultureInfo("de"),
+ ["pt-PT"] = new CultureInfo("pt-PT"),
+ ["pt-BR"] = new CultureInfo("pt-BR"),
+ ["ko-KR"] = new CultureInfo("ko-KR"),
+ ["uk-UA"] = new CultureInfo("uk-UA"),
+ ["cs-CZ"] = new CultureInfo("cs-CZ"),
+ };
public static IReadOnlyList SupportedCultures =>
SupportedCulturesByCode.Values.ToImmutableList();
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
index 3afe98f42..457069730 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
+++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
@@ -785,6 +785,15 @@ public static string AnalyticsExample_InstallData {
}
}
+ ///
+ /// Looks up a localized string similar to Enter a new name for '{0}'.
+ ///
+ public static string Description_RenamePackage {
+ get {
+ return ResourceManager.GetString("Description_RenamePackage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Please select a download location..
///
@@ -974,6 +983,15 @@ public static string Label_AugmentationLevel {
}
}
+ ///
+ /// Looks up a localized string similar to Author.
+ ///
+ public static string Label_Author {
+ get {
+ return ResourceManager.GetString("Label_Author", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Auto Completion.
///
@@ -1091,6 +1109,24 @@ public static string Label_Branches {
}
}
+ ///
+ /// Looks up a localized string similar to Bulk Download Started.
+ ///
+ public static string Label_BulkDownloadStarted {
+ get {
+ return ResourceManager.GetString("Label_BulkDownloadStarted", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} files have started downloading. Check the Downloads tab for progress..
+ ///
+ public static string Label_BulkDownloadStartedMessage {
+ get {
+ return ResourceManager.GetString("Label_BulkDownloadStartedMessage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Callstack.
///
@@ -1514,6 +1550,15 @@ public static string Label_DisplayName {
}
}
+ ///
+ /// Looks up a localized string similar to Download All Files (All Versions).
+ ///
+ public static string Label_DownloadAllFilesAllVersions {
+ get {
+ return ResourceManager.GetString("Label_DownloadAllFilesAllVersions", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Download Failed.
///
@@ -1532,6 +1577,24 @@ public static string Label_Downloads {
}
}
+ ///
+ /// Looks up a localized string similar to Download Started.
+ ///
+ public static string Label_DownloadStarted {
+ get {
+ return ResourceManager.GetString("Label_DownloadStarted", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} will be saved to {1}.
+ ///
+ public static string Label_DownloadWillBeSavedToLocation {
+ get {
+ return ResourceManager.GetString("Label_DownloadWillBeSavedToLocation", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Drag & Drop checkpoints here to import.
///
@@ -1550,6 +1613,15 @@ public static string Label_DropFileToImport {
}
}
+ ///
+ /// Looks up a localized string similar to Early Access Models.
+ ///
+ public static string Label_EarlyAccessModels {
+ get {
+ return ResourceManager.GetString("Label_EarlyAccessModels", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Edit Model Metadata.
///
@@ -1694,6 +1766,33 @@ public static string Label_FatWarning {
}
}
+ ///
+ /// Looks up a localized string similar to File Name Pattern.
+ ///
+ public static string Label_FileNamePattern {
+ get {
+ return ResourceManager.GetString("Label_FileNamePattern", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Files.
+ ///
+ public static string Label_Files {
+ get {
+ return ResourceManager.GetString("Label_Files", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Filter.
+ ///
+ public static string Label_Filter {
+ get {
+ return ResourceManager.GetString("Label_Filter", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Find Connected Metadata.
///
@@ -1766,6 +1865,15 @@ public static string Label_General {
}
}
+ ///
+ /// Looks up a localized string similar to Hash.
+ ///
+ public static string Label_Hash {
+ get {
+ return ResourceManager.GetString("Label_Hash", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Height.
///
@@ -1820,6 +1928,15 @@ public static string Label_HuggingFace {
}
}
+ ///
+ /// Looks up a localized string similar to You must be logged in to download this checkpoint. Please enter a Hugging Face Access Token in the settings..
+ ///
+ public static string Label_HuggingFaceLoginRequired {
+ get {
+ return ResourceManager.GetString("Label_HuggingFaceLoginRequired", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Image Hidden.
///
@@ -1910,6 +2027,15 @@ public static string Label_Inference {
}
}
+ ///
+ /// Looks up a localized string similar to Inference Defaults.
+ ///
+ public static string Label_InferenceDefaultsHeader {
+ get {
+ return ResourceManager.GetString("Label_InferenceDefaultsHeader", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Infinite Scrolling.
///
@@ -1973,6 +2099,15 @@ public static string Label_Installed {
}
}
+ ///
+ /// Looks up a localized string similar to Installed Models.
+ ///
+ public static string Label_InstalledModels {
+ get {
+ return ResourceManager.GetString("Label_InstalledModels", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Install Extension Pack.
///
@@ -2036,6 +2171,15 @@ public static string Label_LastPage {
}
}
+ ///
+ /// Looks up a localized string similar to Last Updated.
+ ///
+ public static string Label_LastUpdatedAt {
+ get {
+ return ResourceManager.GetString("Label_LastUpdatedAt", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Let's get started.
///
@@ -2081,6 +2225,15 @@ public static string Label_LocalModel {
}
}
+ ///
+ /// Looks up a localized string similar to Login required to download this model.
+ ///
+ public static string Label_LoginRequired {
+ get {
+ return ResourceManager.GetString("Label_LoginRequired", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Logs.
///
@@ -2297,6 +2450,15 @@ public static string Label_NoImageFound {
}
}
+ ///
+ /// Looks up a localized string similar to Non-Model Files.
+ ///
+ public static string Label_NonModelFiles {
+ get {
+ return ResourceManager.GetString("Label_NonModelFiles", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to None.
///
@@ -2324,6 +2486,15 @@ public static string Label_NSFW {
}
}
+ ///
+ /// Looks up a localized string similar to NSFW Content.
+ ///
+ public static string Label_NsfwContent {
+ get {
+ return ResourceManager.GetString("Label_NsfwContent", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Number Format.
///
@@ -2639,6 +2810,15 @@ public static string Label_PythonVersionInfo {
}
}
+ ///
+ /// Looks up a localized string similar to Unsupported Python Versions.
+ ///
+ public static string Label_PythonVersionWarningTitle {
+ get {
+ return ResourceManager.GetString("Label_PythonVersionWarningTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to PyTorch Version.
///
@@ -2855,6 +3035,15 @@ public static string Label_SharedModelStrategyShort {
}
}
+ ///
+ /// Looks up a localized string similar to Show.
+ ///
+ public static string Label_Show {
+ get {
+ return ResourceManager.GetString("Label_Show", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Show Model Images.
///
@@ -2891,6 +3080,15 @@ public static string Label_ShowPixelGridAtHighZoomLevels {
}
}
+ ///
+ /// Looks up a localized string similar to Show Unsupported Python Versions.
+ ///
+ public static string Label_ShowUnsupportedPythonVersions {
+ get {
+ return ResourceManager.GetString("Label_ShowUnsupportedPythonVersions", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Skip first-time setup.
///
@@ -3080,6 +3278,24 @@ public static string Label_UnknownPackage {
}
}
+ ///
+ /// Looks up a localized string similar to You may encounter problems with some packages when using unsupported Python versions.
+ ///
+ public static string Label_UnsupportedPythonVersionDetails {
+ get {
+ return ResourceManager.GetString("Label_UnsupportedPythonVersionDetails", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This will show all available Python versions, including those that are not supported by Stability Matrix. Are you sure?.
+ ///
+ public static string Label_UnsupportedPythonVersionWarningDescription {
+ get {
+ return ResourceManager.GetString("Label_UnsupportedPythonVersionWarningDescription", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Update Available.
///
@@ -3215,6 +3431,15 @@ public static string Label_VideoQuality {
}
}
+ ///
+ /// Looks up a localized string similar to View.
+ ///
+ public static string Label_View {
+ get {
+ return ResourceManager.GetString("Label_View", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Waiting to connect....
///
@@ -3868,6 +4093,33 @@ public static string TextTemplate_YouCanChangeThisBehavior {
}
}
+ ///
+ /// Looks up a localized string similar to When enabled, these settings will be applied automatically when this model is selected in the Inference tab.
+ ///
+ public static string Tooltip_InferenceDefaults {
+ get {
+ return ResourceManager.GetString("Tooltip_InferenceDefaults", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Package name cannot be empty.
+ ///
+ public static string Validation_PackageNameCannotBeEmpty {
+ get {
+ return ResourceManager.GetString("Validation_PackageNameCannotBeEmpty", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Package named '{0}' already exists.
+ ///
+ public static string ValidationError_PackageExists {
+ get {
+ return ResourceManager.GetString("ValidationError_PackageExists", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to PLEASE EXTRACT THE APP FROM THE ZIP FILE BEFORE RUNNING STABILITY MATRIX.
///
@@ -3876,5 +4128,14 @@ public static string Warning_PleaseExtractFirst {
return ResourceManager.GetString("Warning_PleaseExtractFirst", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Enter package name.
+ ///
+ public static string Watermark_EnterPackageName {
+ get {
+ return ResourceManager.GetString("Watermark_EnterPackageName", resourceCulture);
+ }
+ }
}
}
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.cs-CZ.resx b/StabilityMatrix.Avalonia/Languages/Resources.cs-CZ.resx
new file mode 100644
index 000000000..3be8cb636
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Languages/Resources.cs-CZ.resx
@@ -0,0 +1,1519 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Přidat pro všechny uživatele
+
+
+ Přidat pro aktuálního uživatele
+
+
+ Přidat balíček
+
+
+ Zrušit
+
+
+ Zkontrolovat aktualizace
+
+
+ Zkontrolovat verzi
+
+
+ Zrušit výběr
+
+
+ Zavřít
+
+
+ Připojit
+
+
+ Konsolidovat
+
+
+ Pokračovat
+
+
+ Kopírovat
+
+
+ Kopírovat a otevřít
+
+
+ Kopírovat jako bitmapu
+
+
+ Kopírovat detaily
+
+
+ Kopírovat spouštěcí slova
+
+
+ Smazat
+
+
+ Zakázat
+
+
+ Odpojit
+
+
+ Downgradovat
+
+
+ Stáhnout
+
+
+ Upravit
+
+
+ Povolit
+
+
+ Ukončit aplikaci
+
+
+ Přejít do Nastavení
+
+
+ Skrýt
+
+
+ Importovat
+
+
+ Instalovat
+
+
+ Instalovat nyní
+
+
+ Spustit
+
+
+ Přihlásit se
+
+
+ Možná později
+
+
+ Přesunout do koše
+
+
+ Nový
+
+
+ OK
+
+
+ Otevřít na GitHubu
+
+
+ Otevřít v prohlížeči
+
+
+ Otevřít v Průzkumníkovi
+
+
+ Otevřít ve Finderu
+
+
+ Otevřít v prohlížeči obrázků
+
+
+ Otevřít na CivitAI
+
+
+ Otevřít na Hugging Face
+
+
+ Otevřít na OpenArt
+
+
+ Otevřít na OpenModelDB
+
+
+ Otevřít projekt...
+
+
+ Otevřít webové UI
+
+
+ Náhled preprocesoru
+
+
+ Ukončit
+
+
+ Obnovit
+
+
+ Restartovat
+
+
+ Restartovat později
+
+
+ Připomenout později
+
+
+ Odebrat
+
+
+ Přejmenovat
+
+
+ Nahradit obsah
+
+
+ Restartovat
+
+
+ Obnovit výchozí rozložení
+
+
+ Zkusit znovu
+
+
+ Uložit
+
+
+ Uložit jako...
+
+
+ Hledat
+
+
+ Vybrat vše
+
+
+ Vybrat adresář
+
+
+ Vybrat soubor
+
+
+ Odeslat
+
+
+ Odeslat vstup
+
+
+ Odeslat do Inference
+
+
+ Zobrazit v Průzkumníkovi
+
+
+ Zobrazit záznam v Průzkumníkovi
+
+
+ Zobrazit záznam ve Finderu
+
+
+ Zaregistrovat se
+
+
+ Zastavit
+
+
+ Přepnout viditelnost
+
+
+ Odinstalovat
+
+
+ Aktualizovat
+
+
+ Aktualizovat stávající metadata
+
+
+ Upgradovat
+
+
+ Zobrazit možnosti podpory
+
+
+ Ano
+
+
+ {
+ "packageName": "stable-diffusion-webui",
+ "packageVersion": "v1.10.0",
+ "isSuccess": true,
+ "type": "install",
+ "timestamp": "2024-09-04T02:14:04.1967404+00:00"
+}
+
+
+ Zadejte nový název pro '{0}'
+
+
+ Vyberte prosím umístění pro stažení.
+
+
+ O aplikaci
+
+
+ Účty
+
+
+ Nastavení účtu
+
+
+ Tuto akci nelze vrátit zpět.
+
+
+ Doplňky
+ Inference Sampler Addons
+
+
+ Přidat do existující sady
+
+
+ Přidat Stability Matrix do nabídky Start
+
+
+ Použije aktuální umístění aplikace, můžete spustit znovu, pokud aplikaci přesunete
+
+
+ Pokročilé možnosti
+
+
+ Všechny verze
+
+
+ Analytika
+
+
+ Jiná instance Stability Matrix je již spuštěna. Před spuštěním nové ji prosím zavřete.
+
+
+ API klíč
+
+
+ Data aplikace
+
+
+ Vzhled
+
+
+ Adresáře aplikace
+
+
+ Jste si jisti?
+
+
+ Jste si jisti, že chcete smazat {0} obrázků?
+
+
+ Jste si jisti, že chcete smazat {0} modelů?
+
+
+ Úroveň augmentace
+
+
+ Autor
+
+
+ Automatické doplňování
+
+
+ Automaticky rolovat na konec výstupu konzole
+
+
+ Automatické hledání při načtení
+
+
+ Automaticky zahájit hledání při načtení stránky prohlížeče modelů
+
+
+ Automatické aktualizace
+
+
+ Základní model
+
+
+ Dávky
+
+
+ Index dávky
+
+
+ Velikost dávky
+
+
+ Staňte se patronem
+
+
+ Beta
+
+
+ Větev
+
+
+ Větve
+
+
+ Hromadné stahování zahájeno
+
+
+ {0} souborů se začalo stahovat. Průběh sledujte v záložce Stahování.
+
+
+ Zásobník volání
+
+
+ Kategorie
+
+
+ CFG Scale
+
+
+ Změny byly aplikovány
+
+
+ Kontrolujeme některé hardwarové specifikace pro určení kompatibility.
+
+
+ Správce Checkpointů
+
+
+ Checkpointy
+
+
+ CivitAI
+
+
+ Pro stažení tohoto Checkpointu musíte být přihlášeni. Zadejte prosím API klíč pro CivitAI v nastavení.
+
+
+ Ořezová maska
+
+
+ CLIP Skip
+
+
+ Síla CLIP
+
+
+ Po dokončení zavřít dialogové okno
+
+
+ Pro instalaci tohoto balíčku je vyžadováno ComfyUI. Chcete jej nyní nainstalovat?
+
+
+ Vyžadováno ComfyUI
+
+
+ Komentáře
+
+
+ Revize
+
+
+ Při vkládání doplňků nahradit podtržítka mezerami
+
+
+ Konfigurace
+
+
+ Potvrdit smazání
+
+
+ Potvrdit ukončení
+
+
+ Opravdu si přejete aplikaci ukončit? Tím se také uzavřou všechny aktuálně spuštěné balíčky.
+
+
+ Potvrdit heslo
+
+
+ Potvrdit?
+
+
+ Nepodařilo se nám připojit váš účet
+
+
+ Připojeno
+
+
+ Připojený model
+
+
+ Připojování...
+
+
+ Konzole
+
+
+ Tímto přesunete všechny vygenerované obrázky z vybraných balíčků do konsolidovaného adresáře ve sdíleném výstupním adresáři. Tuto akci nelze vrátit zpět.
+
+
+ Kroky ControlNetu
+
+
+ Váha ControlNetu
+
+
+ Kopírovat odkaz do schránky
+
+
+ Pro vytvoření sady jednoduše vyberte požadovaná rozšíření z karty 'Dostupná rozšíření' nebo 'Nainstalovaná rozšíření' a klikněte na 'Uložit'
+
+
+ Aktuální adresář:
+
+
+ Adresář s daty
+
+
+ Zde budou instalována data aplikace (modelové Checkpointy, webová UI atd.).
+
+
+ Název adresáře s daty
+
+
+ Data poskytuje CivitAI
+
+
+ Potlačení
+
+
+ Smazat trvale
+
+
+ Síla odšumění
+
+
+ Specifikátory závislostí
+
+
+ Detaily
+
+
+ Vypnout kontrolu aktualizace
+
+
+ Discord Rich Presence
+
+
+ Zobrazovaný název
+
+
+ Stáhnout všechny soubory (všechny verze)
+
+
+ Stažení se nezdařilo
+
+
+ Stahování
+
+
+ Stahování zahájeno
+
+
+ {0} bude uložen do {1}
+
+
+ Přetáhněte Checkpointy sem pro import
+
+
+ Přetáhněte soubor sem pro import
+
+
+ Modely s předběžným přístupem
+
+
+ Upravit metadata modelu
+
+
+ E-mail
+
+
+ Integrovaný Python
+
+
+ Embeddings / Textual Inversion
+
+
+ Zvýraznění
+
+
+ Povolit dlouhé cesty
+ (Setting to enable long file paths on windows)
+
+
+ Odstranit omezení MAX_PATH z běžných funkcí Win32 pro soubory a adresáře
+ (Setting to enable long file paths on windows)
+
+
+ Proměnné prostředí
+
+
+ Název
+
+
+ Hodnota
+
+
+ Chyba při instalaci balíčku
+
+
+ Chyba při načítání Workflows
+
+
+ Všechno vypadá v pořádku!
+
+
+ Sady rozšíření
+
+
+ Extra sítě (Lora / LyCORIS)
+
+
+ Při použití disku s formátem FAT32 nebo exFAT může docházet k chybám. Pro bezproblémový chod zvolte jiný disk.
+
+
+ Vzor názvu souboru
+
+
+ Soubory
+
+
+ Filtr
+
+
+ Najít připojená metadata
+
+
+ Najít v Prohlížeči modelů
+
+
+ Dokončen import Workflow a vlastních uzlů
+
+
+ První stránka
+
+
+ Adresář
+
+
+ Snímků za sekundu
+
+
+ Snímky
+
+
+ Obecné
+ A general settings category
+
+
+ Hash
+
+
+ Výška
+
+
+ Skrýt prázdné kategorie
+
+
+ Velikost historie
+
+
+ Počet řádků nad těmi zobrazenými v konzoli, ke kterým se můžete vrátit
+
+
+ Prázdninový režim
+
+
+ Hugging Face
+
+
+ Pro stažení tohoto Checkpointu musíte být přihlášeni. Zadejte prosím přístupový token pro Hugging Face v nastavení.
+
+
+ Obrázek skryt
+
+
+ Obrázek na obrázek
+
+
+ Obrázek na video
+
+
+ Prohlížeč obrázků
+
+
+ Importovat s metadaty
+
+
+ Hledat připojená metadata u nových lokálních importů
+
+
+ Přidejte soubor sady rozšíření .json do adresáře ExtensionPacks ve vašem adresáři s daty.
+
+
+ Importovat nejnovější -
+
+
+ Indexování...
+
+
+ Inference
+ The Inference feature page
+
+
+ Výchozí nastavení Inference
+
+
+ Nekonečné posouvání
+
+
+ Vnitřní výjimka
+
+
+ Inpainting
+
+
+ Vstup
+
+
+ Vyžadován vstup
+
+
+ Instalace s tímto názvem již existuje.
+
+
+ Nainstalováno
+
+
+ Nainstalované modely
+
+
+ Instalovat sadu rozšíření
+
+
+ Instaluje se
+
+
+ Integrace
+
+
+ Neplatný typ balíčku
+
+
+ Připojit se na Discord server
+
+
+ Jazyk
+
+
+ Poslední stránka
+
+
+ Poslední aktualizace
+
+
+ Začněme
+
+
+ Licence
+
+
+ licenčním ujednáním.
+
+
+ Licence a upozornění na open source
+
+
+ Lokální model
+
+
+ Pro stažení tohoto modelu je vyžadováno přihlášení
+
+
+ Záznamy (logy)
+
+
+ Bezztrátové
+
+
+ Maximální velikost
+
+
+ Min CFG
+
+
+ Chybějící soubor obrázku
+
+
+ Model
+
+
+ Prohlížeč modelů
+
+
+ Popis modelu
+
+
+ Hledat modely,
+
+
+ Adresář modelů
+
+
+ Typ modelu
+
+
+ Motion Bucket ID
+
+
+ Negativní prompt
+
+
+ Sítě (Lora / LyCORIS)
+
+
+ Nová sada rozšíření
+
+
+ Nový adresář
+
+
+ Je dostupná nová verze Stability Matrix!
+
+
+ Další obrázek
+
+
+ Další stránka
+
+
+ Ne
+
+
+ Detaily uzlu
+
+
+ Nebyly nalezeny žádné sady rozšíření
+
+
+ Nebyla nalezena žádná rozšíření.
+
+
+ Nebyl nalezen žádný obrázek
+
+
+ Soubory, které nejsou modely
+
+
+ Žádné
+
+
+ Upozornění
+
+
+ NSFW
+
+
+ NSFW obsah
+
+
+ Formát čísla
+
+
+ {0} obrázků vybráno
+
+
+ Pro nejlepší zážitek doporučujeme GPU s podporou CUDA. Můžete pokračovat i bez ní, ale některé balíčky nemusí fungovat a inference může být pomalejší.
+
+
+ 1 obrázek vybrán
+
+
+ Dostupné pouze ve Windows
+
+
+ Prohlížeč OpenArt
+
+
+ Otevřít adresář sad rozšíření
+
+
+ - nebo -
+
+
+ Výstupní adresář
+
+
+ Výstupní soubory obrázků
+
+
+ Prohlížeč výstupů
+
+
+ Typ výstupu
+
+
+ Prostředí balíčku
+
+
+ Balíčky
+
+
+ Typ balíčku
+
+
+ Balíček odinstalován
+
+
+ Stránka
+
+
+ Heslo
+
+
+ Zvolte prosím jiný název nebo vyberte jiné umístění instalace.
+
+
+ Vyberte prosím adresář s daty
+
+
+ Přenosný režim
+
+
+ V přenosném režimu budou všechna data a nastavení uložena ve stejném adresáři jako aplikace. Aplikaci budete moci přesunout i s jejím adresářem 'Data' na jiné místo nebo počítač.
+
+
+ Preprocesor
+
+
+ Náhledový obrázek
+
+
+ Předchozí obrázek
+
+
+ Předchozí stránka
+
+
+ Zásady ochrany osobních údajů
+
+
+ Prompt
+ A settings category for Inference generation prompts
+
+
+ Tagy pro prompt
+ Tags for image generation prompts
+
+
+ Soubor s tagy pro navrhování doplňků (podporuje formát .csv z a1111-sd-webui-tagcomplete)
+
+
+ Importovat tagy pro prompt
+
+
+ Přidat, nahradit nebo odebrat závislosti pro instalaci a aktualizaci
+
+
+ Přepsání závislostí Pythonu
+
+
+ Balíčky Pythonu
+
+
+ Informace o verzi Pythonu
+
+
+ Nepodporované verze Pythonu
+
+
+ Verze PyTorch
+
+
+ Přečetl/a jsem si a souhlasím s
+
+
+ Doporučené modely
+
+
+ Zatímco se váš balíček instaluje, zde je několik modelů, které doporučujeme pro začátek.
+
+
+ Refiner
+
+
+ Vyžadován restart
+
+
+ Poznámky k vydání
+
+
+ Vydání
+
+
+ Pro tento balíček nejsou dostupná žádná vydání.
+
+
+ Při vypnutí odstranit symbolické odkazy sdíleného adresáře Checkpointů
+
+
+ Tuto možnost vyberte, pokud máte problémy s přesunem Stability Matrix na jiný disk
+
+
+ Resetovat mezipaměť Checkpointů
+
+
+ Znovu sestaví mezipaměť nainstalovaných Checkpointů. Použijte, pokud jsou Checkpointy nesprávně označeny v Prohlížeči modelů
+
+
+ Sampler
+
+
+ Uložit mezilehlý obrázek
+ Inference module step to save an intermediate image
+
+
+ Scheduler
+
+
+ Hledat...
+
+
+ Seed
+
+
+ Vyberte umístění pro stažení:
+
+
+ Vybrat nový adresář s daty
+
+
+ Nepřesouvá existující data. Vyžaduje restart aplikace.
+
+
+ Nastavení
+
+
+ Strategie sdíleného adresáře modelů
+
+
+ Sdílení modelů
+
+
+ Zobrazit
+
+
+ Zobrazit obrázky modelů
+
+
+ Zobrazit NSFW obsah
+
+
+ Zobrazit NSFW obrázky
+
+
+ Zobrazit mřížku pixelů při vysokém přiblížení
+
+
+ Zobrazit nepodporované verze Pythonu
+
+
+ Přeskočit úvodní nastavení
+
+
+ Seřadit
+
+
+ Stability Matrix
+
+
+ Stability Matrix je již spuštěna
+
+
+ Stav
+
+
+ Kroky
+
+
+ Kroky - Základ
+
+
+ Kroky - Refiner
+
+
+ Síla
+
+
+ Systém
+
+
+ Systémové informace
+
+
+ Nastavení systému
+
+
+ Tagy
+
+
+ Text na obrázek
+
+
+ Motiv
+
+
+ Časové období
+
+
+ Automatické rolování na konec
+
+
+ Trénovaná slova
+
+
+ Spouštěcí slova:
+
+
+ Došlo k neočekávané chybě
+
+
+ Neznámý balíček
+
+
+ Při použití nepodporovaných verzí Pythonu se můžete setkat s problémy u některých balíčků
+
+
+ Tímto se zobrazí všechny dostupné verze Pythonu, včetně těch, které nejsou podporovány Stability Matrix. Jste si jisti?
+
+
+ Dostupná aktualizace
+
+
+ Aktualizace
+
+
+ Pro technické uživatele. Získejte jako první přístup k našim vývojovým verzím z funkčních větví, jakmile budou dostupné. Mohou se vyskytnout nedokonalosti a chyby, protože experimentujeme s novými funkcemi.
+
+
+ Pro první uživatele. Preview verze budou spolehlivější než ty z Dev kanálu a budou dostupné blíže ke stabilním vydáním. Vaše zpětná vazba nám výrazně pomůže při odhalování problémů a ladění designových prvků.
+
+
+ Upscale
+
+
+ Data o používání
+
+
+ Uživatelské jméno
+
+
+ Použít oddělený prompt
+
+
+ Sdílení výstupního adresáře
+
+
+ VAE
+
+
+ Verze
+
+
+ Název verze
+
+
+ Typ verze
+
+
+ Metoda
+
+
+ Kvalita
+
+
+ Zobrazení
+
+
+ Čekání na připojení...
+
+
+ Webové UI
+
+
+ Šířka
+
+
+ Žolíky (Wildcards)
+
+
+ Zatím nedostupné
+
+
+ Funkce bude dostupná v budoucí aktualizaci
+
+
+ Prohlížeč Workflow
+
+
+ Workflow smazán
+
+
+ {0} úspěšně smazán
+
+
+ Popis Workflow
+
+
+ Workflow a vlastní uzly byly importovány.
+
+
+ Workflow importován
+
+
+ Workflows
+
+
+ Máte aktuální verzi
+
+
+ Aktivní
+
+
+ Zažijte rychlejší výsledky vyhledávání při procházení modelů z online repozitářů, jako je Civitai.
+
+
+ Experimentální optimalizace pro repozitáře třetích stran. Není oficiálně přidruženo; dostupnost se může lišit.
+
+
+ Neaktivní (používá se standardní připojení)
+
+
+ Zrychlené objevování modelů
+
+
+ Stahování dokončeno
+
+
+ Stahování balíčku...
+
+
+ Instalace dokončena
+
+
+ Instalace požadavků balíčku...
+
+
+ Instalace předpokladů...
+
+
+ Odinstalovávání balíčku...
+
+
+ Aktualizace dokončena
+
+
+ Aktualizace se nezdařila
+
+
+ ### 🪄 Představujeme: Zesilovač promptů
+Náš AI asistent, poháněný naším experimentálním modelem Spark, bude generovat kreativní rozšíření vašeho promptu.
+
+Zesilovač promptů běží v našem zabezpečeném, podnikovém cloudovém prostředí – **neběží** lokálně na vašem počítači.
+
+### ☁️️ Proč cloud?
+Model Spark pracuje v měřítku srovnatelném se základními modely s biliony parametrů, což vyžaduje značný výpočetní výkon. I když jsme odhodláni maximalizovat lokálně spustitelné funkce, pokročilé schopnosti Sparku jsou dostupné **nyní** prostřednictvím naší cloudové infrastruktury.
+
+### 🔒 Soukromí na prvním místě
+Upřednostňujeme vaše soukromí ([Podmínky Gen AI](<https://lykos.ai/gen-ai-terms>)). **Vaše prompty/výstupy NEJSOU NIKDY používány pro trénování AI společností Lykos AI ani našimi nezbytnými partnery pro cloudovou infrastrukturu.** Bezpečné zpracování probíhá pouze za účelem generování vašeho zesílení, **poté si ponecháváme pouze metadata (jako jsou časová razítka a počty tokenů), nikoli samotný obsah promptu.** Vaše data se nikdy neprodávají ani nesdílejí.
+
+
+ Pokud nás již podporujete na Patreonu, propojte prosím svůj účet pro pokračování.
+
+
+ Děkujeme, že podporujete Stability Matrix!
+
+
+ Funkce jako **{0}** jsou jednou z mnoha výhod dostupných našim podporovatelům. Váš příspěvek nám pomáhá pokrýt náklady na servery a podporuje vývoj Stability Matrix.
+
+
+ Funkce jako **{0}** jsou dostupné na úrovni podpory **{1}** (nebo vyšší). Váš příspěvek nám pomáhá pokrýt náklady na servery pro pokročilejší propojené funkce a umožňuje nám neustále vylepšovat Stability Matrix pro všechny.
+
+
+ Podpořte Stability Matrix
+
+
+ Nainstalované Workflows
+
+
+ Pro začátek přidejte balíček!
+
+
+ Pro zahájení klikněte na Spustit!
+
+
+ Zde sledujte postup instalací balíčků a stahování modelů.
+
+
+ Klikněte zde pro přehled syntaxe promptu a jak zahrnout Lora / Embeddings.
+
+
+ Zde lze povolit další adresáře, jako jsou IPAdapters a TextualInversions (embeddings)
+
+
+ Vyzkoušejte nový Zesilovač promptů! Vylepšete své prompty pro lepší výsledky!
+
+
+ Tlačítko 'Otevřít webové UI' bylo přesunuto do příkazové lišty
+
+
+ Povolte prosím svému prohlížeči otevřít tuto aplikaci, když budete vyzváni k pokračování.
+
+
+ Odeslaná data nejsou nikdy spojena s vámi nebo vaším účtem a nebudou obsahovat osobní údaje ani žádné citlivé informace.
+
+
+ Pomozte nám vylepšit Stability Matrix zasíláním anonymních dat o používaných funkcích, verzích operačního systému, typech instalovaných balíčků atd. Odeslaná data nejsou nikdy spojena s vámi nebo vaším účtem a nebudou obsahovat osobní údaje ani žádné citlivé informace.
+
+
+ Pomozte nám vylepšit Stability Matrix zasíláním anonymních dat o používaných funkcích, verzích operačního systému, typech instalovaných balíčků atd.
+
+
+ Aplikace se po aktualizaci restartuje
+
+
+ Chystáte se smazat následující položky:
+
+
+ Přihlaste se svým účtem Lykos pro použití propojených funkcí.
+
+
+ Připojte svůj účet Lykos
+
+
+ Vaše přihlášení vypršelo. Pro pokračování se prosím znovu přihlaste.
+
+
+ Přihlaste se prosím znovu
+
+
+ ## Upozornění: Odhlášení z účtu Lykos a vylepšení zabezpečení
+
+Provedli jsme několik důležitých vylepšení v zákulisí, jak Stability Matrix nakládá s účty Lykos, a přešli jsme na bezpečnější a pohodlnější systém přihlašování **(OAuth 2.0 s OpenID Connect)**. Z tohoto důvodu jste byli odhlášeni ze svého účtu Lykos.
+
+### Proč tato změna?
+
+Vaše bezpečnost a soukromí jsou pro nás důležité. Toto vylepšení přináší:
+
+* **Zjednodušené prostředí:** Přihlaste se jednou na [account.lykos.ai](https://account.lykos.ai) pro spojení se Stability Matrix a dalšími službami Lykos AI.
+* **Více způsobů přihlášení:** Použijte svůj stávající účet [lykos.ai](https://lykos.ai) nebo se přihlaste pomocí **Apple**, **GitHub** nebo **Google**.
+* **Zvýšené soukromí:** Stability Matrix požaduje pouze oprávnění, která potřebuje.
+* **Bezpečnostní standard:** Používáme OAuth 2.0, zlatý standard pro bezpečné přihlašování.
+* **Připraveno na budoucnost:** To umožňuje bezpečné spojení s dalšími aplikacemi a službami.
+
+### Co mám dělat?
+
+Klikněte na tlačítko **"Přejít do Nastavení"** a poté klikněte na **"Připojit"** vedle **"Účet Lykos"**.
+
+### Je účet Lykos vyžadován?
+
+Ne! Stability Matrix je stále plně funkční i bez něj. Váš účet Lykos ale umožňuje některé extra propojené funkce, jako jsou automatické aktualizace vývojových verzí pro naše podporovatele na Patreonu (a další přijdou!).
+
+
+
+ Otevřete odkaz ve svém prohlížeči a zadejte následující kód pro autorizaci vašeho účtu se Stability Matrix.
+
+
+ Otevřete odkaz ve svém prohlížeči a postupujte podle pokynů pro připojení vašeho účtu.
+
+
+ Vyberte si preferované rozhraní a začněte
+
+
+ Tímto smažete adresář balíčku a veškerý jeho obsah, včetně všech vygenerovaných obrázků a souborů, které jste mohli přidat.
+
+
+ Přechod na stránku Spuštění
+
+
+ Pro aplikování nového jazyka je vyžadován restart aplikace.
+
+
+ Pro projevení systémových změn může být vyžadován restart.
+
+
+ Některé soubory se nepodařilo smazat. Zavřete prosím všechny otevřené soubory v adresáři balíčku a zkuste to znovu.
+
+
+ Informujte nás prosím o tomto problému s níže uvedenými detaily a přiložte zazipované soubory se záznamy (logy).
+
+
+ Můžete pokračovat, ale plná funkčnost bude dostupná až po restartu. Informujte nás prosím o tomto problému s níže uvedenými detaily a přiložte zazipované soubory se záznamy (logy).
+
+
+ Vítejte ve Stability Matrix!
+
+
+ Chystáte se smazat následujících {0} položek:
+
+
+ Chyba při aktualizaci {0}
+
+
+ Poslední kontrola: {0}
+
+
+ Přihlásit se pomocí {0}
+ e.g. 'Sign in with Google'
+
+
+ {0} byl aktualizován na nejnovější verzi
+
+
+ {0} byl aktualizován na vybranou verzi
+
+
+ Aktualizace {0}
+
+
+ Toto chování můžete kdykoli změnit v {0}.
+ e.g. 'You can always change this behavior in Settings > Category > Item.'
+
+
+ Pokud je povoleno, tato nastavení se automaticky použijí, když je tento model vybrán v záložce Inference
+
+
+ Název balíčku nemůže být prázdný
+
+
+ Balíček s názvem '{0}' již existuje
+
+
+ PŘED SPUŠTĚNÍM STABILITY MATRIX PROSÍM ROZBALTE APLIKACI ZE ZIP SOUBORU
+
+
+ Zadejte název balíčku
+
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx b/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx
index 27ee43b4c..c416b6dcd 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx
@@ -119,7 +119,6 @@
Launch
- In Japanese, the literal translation of "launch" can be misinterpreted as "turning on the PC". After much deliberation, I decided that it would be easier to understand if "launch" were not translated.
終了
@@ -146,8 +145,7 @@
再起動が必要です
- 不明なパッケージ
-
+ 不明なパッケージ
インポート
@@ -165,22 +163,19 @@
Releases
- ブランチ
-
+ ブランチ
- インポートしたいcheckpointをここにドラッグ&ドロップ
- checkpoint / embedding / LoRA are often used in the same way as the above words on Japanese information websites, so it is easier to understand them without translation.
+ インポートするCheckpointをここにドラッグ&ドロップ
プロンプトの強調
- The word has not been translated because it is not possible to guess what part of the UI it is used in. It is a little difficult to translate the word into Japanese, so I want to be careful not to change the meaning of the word.
- プロンプトの縮小
+ プロンプトを弱める
- Emebeddings / Textual Inversion
+ Embeddings / Textual Inversion
Networks (Lora / LyCORIS)
@@ -219,7 +214,7 @@
VAE
- Model
+ モデル
接続
@@ -238,7 +233,6 @@
Patreonになる
- fix platform name
Discordに参加
@@ -250,19 +244,19 @@
インストール
- セットアップをスキップする
+ セットアップをスキップ
予期せぬエラーが発生しました
- アプリケーションを終了する
+ アプリケーションを終了
表示名
- 同じ名前のものが既に存在します。
+ 同じ名前が既に存在します。
別の名前を選択するか、別のインストール場所を選択してください。
@@ -284,10 +278,9 @@
データフォルダ
- I think there are many windows users, so I changed the word "directory" to "folder".
- ここにcheckpoint、LORA、Web UI、設定などがインストールされます。
+ ここにCheckpoint、LORA、Web UI、設定などがインストールされます。
フォーマット形式がFAT32またはexFATのドライブを使用するとエラーが発生する場合があります。他のドライブを選択することで、よりスムーズにご利用いただけます。
@@ -296,7 +289,7 @@
Portableモード
- Portableモードでは、すべてのデータと設定はアプリケーションと同じフォルダに保存されます。アプリケーションと、その「Data」フォルダを一緒に移動させることで、別のフォルダや別のコンピュータに移すことができます。
+ Portableモードではすべてのデータと設定はアプリケーションと同じフォルダに保存されます。アプリケーションと「Data」フォルダを一緒に移動させることで、別のフォルダや別のコンピュータに移すことができます。
続ける
@@ -308,10 +301,10 @@
次の画像
- Modelの説明
+ モデルの説明
- Stability Matrixを最新版に更新中!
+ Stability Matrixの最新版がリリース!
最新版DL
@@ -341,7 +334,7 @@
NSFWコンテンツを表示
- Data provided by CivitAI
+ CivitAIによる情報
ページ
@@ -369,7 +362,6 @@
メタデータ取得済みモデル
- i rewrited "model got metadata". The reason for this is that when translated into Japanese, it was difficult to understand what the connection was to if only "connected" was used.
ローカルモデル
@@ -388,7 +380,6 @@
インポート時にメタデータを自動検索
- "metadata retrieval on import", This is also because it was difficult to understand what "online" means in Japanese.
ローカルからのインポート時にオンラインでメタデータを検索して適用します
@@ -424,24 +415,22 @@
テーマ
- Checkpoint Manager
+ Checkpointマネージャー
- checkpointフォルダ内のシンボリックリンクをシャットダウンか再起動時に削除
- I had mistranslated and rewrite now. I thought it was "when the software exits," but then I realized, given the .net source, that this was to be executed at OS shutdown.
+ Checkpointフォルダ内のシンボリックリンクをシャットダウンか再起動時に削除
Stability Matrix を別のドライブに移動する際に問題が起きた場合、ここにチェック
- It may be better to use a variable like {0} for the app name
Checkpointキャッシュのリセット
- checkpointsキャッシュを再構築します。Model Browserでcheckpointsのラベルが正しくない場合に使用してください
+ Checkpointsキャッシュを再構築します。モデルブラウザでCheckpointsのラベルが正しくない場合に使用してください
- パッケージの環境
+ パッケージ環境
編集
@@ -459,7 +448,7 @@
統合
- Stability Matrix利用中、Discordステータス欄に表示
+ Stability Matrix利用中にDiscordステータス欄に表示
システム
@@ -578,10 +567,10 @@
パッケージを追加して始めよう!
- 名称
+ 変数名
- Value
+ 値
削除
@@ -623,7 +612,7 @@
パッケージのアンインストール
- 一部のファイルを削除できませんでした。該当のディレクトリ内にあるファイルで開いていたものを全て閉じて、もう一度試してください。
+ 一部のファイルを削除できませんでした。該当のフォルダの開いているファイルを全て閉じて、もう一度試してください。
無効なパッケージタイプ
@@ -651,7 +640,6 @@
Branch
- For Japanese engineers who use git on a daily basis, it is easier to understand terms used in git as they are in English.
コンソール画面の最後まで自動スクロールする
@@ -678,7 +666,7 @@
また後で
- Install Now
+ インストールする
リリースノート
@@ -750,7 +738,7 @@
よろしいですか?
- これにより、選択したパッケージから生成されたすべてのイメージが、共有出力フォルダの Consolidated ディレクトリに移動します。この操作は元に戻せません。
+ これにより、選択したパッケージから生成されたすべてのイメージが、共有出力フォルダの Consolidated フォルダに移動します。この操作は元に戻せません。
更新
@@ -889,7 +877,7 @@
Hugging Face
- Addons
+ アドオン
Inference Sampler Addons
@@ -925,7 +913,6 @@
フレームレート(FPS)
- for jp, "frame rate" is easier to understand, and its better to append FPS. no one can mistake it for a genre of games except nerds
Min CFG
@@ -940,7 +927,7 @@
Motion Bucket ID
- Augmentation Level
+ 拡張レベル
Method
@@ -1027,13 +1014,13 @@
いい感じ!
- 快適な利用にはCUDA対応GPUを推奨します。それ以外だと一部のパッケージが動作しなかったり、動くけど遅い場合があります。
+ 快適な利用にはCUDA対応GPUを推奨します。非対応の場合一部のパッケージが動作しない場合や、動作が遅くなる場合があります。
Checkpoints
- Model Brouser
+ モデルブラウザ
ワークフロー
@@ -1063,10 +1050,10 @@
'Web UIを開く'ボタンがコマンドバーに移動しました
- Stability Matrixは既に立ち上がってます。それを終了させてから、もう一度起動してください。
+ Stability Matrixは既に起動中です。終了してから、もう一度起動してください。
- Stability Matrixは既に立ち上がってます
+ Stability Matrixは既に起動中です
{0} 個削除しました
@@ -1117,10 +1104,10 @@
{0}個のモデルを削除しますか?
- Auto-Search on Load
+ ロード時に自動的に検索
- model browserページが読み込まれたら、自動的に検索を開始する
+ モデルブラウザが読み込まれたら、自動的に検索を開始する
表示の切り替え
@@ -1149,6 +1136,9 @@
Stability Matrixを実行する前に、ZIPファイルからアプリを抽出してください
+
+ 履歴数
+
コンソールに表示されている行より上にスクロールして戻ることができる行数
@@ -1158,9 +1148,48 @@
モデルメタデータの編集
+
+ NSFW
+
+
+ タグ
+
バージョン名
+
+ 言葉を学習する
+
+
+ 前の画像
+
+
+ Batch Size
+
+
+ Batches
+
+
+ サンプラー
+
+
+ スケジューラー
+
+
+ 最大数
+
+
+ 分割プロンプトを使う
+
+
+ シード
+
+
+ Negative Prompt
+
+
+ 新しいフォルダ
+
リンクをクリップボードにコピー
@@ -1168,6 +1197,9 @@
{0} でサインイン
e.g. 'Sign in with Google'
+
+ ブラウザ上でこのアプリを開くように指示されますので、その通りにしてください。
+
ブラウザのリンクを開き、指示に従ってアカウントを接続してください
@@ -1189,6 +1221,9 @@
"timestamp": "2024-09-04T02:14:04.1967404+00:00"
}
+
+ アナリティクス
+
この動作はいつでも {0} の手順で変更できます。
e.g. 'You can always change this behavior in Settings > Category > Item.'
@@ -1208,6 +1243,9 @@
プライバシーポリシー
+
+ 画像を隠す
+
画像が見つかりません
@@ -1237,85 +1275,161 @@
このパッケージではこのリリースを利用できません。
+
+ この問題の詳細を、圧縮したログファイルとともにお教えください。
+
+
+ この後も続けて使えますが、再起動後にすべての機能を使えるようになります。この問題の詳細を、圧縮したログファイルとともにお教えください。
+
+
+ エクスプローラでログを開く
+
+
+ Finderでログを開く
+
+
+ Extension Packs
+
+
+ Extension Packsが見つかりません
+
+
+ Extension Packフォルダを開く
+
+
+ Extension Packをインストール
+
+
+ Extension Packを追加
+
+
+ 新しいExtension Pack
+
+
+ 拡張機能を作成するには、"利用可能な拡張機能"または"インストール済みの拡張機能"タブから欲しい拡張機能を選んで保存をクリック
+
+
+ Dataディレクトリ内のExtensionPacksフォルダに.json拡張パックファイルを追加してください。
+
+
+ - または -
+
+
+ OpenModelDBを開く
+
+
+ Wildcards
+
+
+ Stability Matrixのアカウント認証のため、ブラウザでリンクを開いて以下のコードを入力してください。
+
+
+ Copy and Open
+
+
+ ## 【重要】 Lykosアカウントのログアウトとセキュリティ強化のお知らせ
+
+Stability MatrixとLykosアカウントの連携を、より安全で便利なシステム(OAuth 2.0 with OpenID Connect)へと変更しました。これに伴い、Lykosアカウントからログアウトされています。
+
+### アップグレードの理由は?
+
+あなたのセキュリティとプライバシーを最重視するため、今回のアップグレードで以下の改善を行いました:
+
+* **よりスムーズなログイン環境:** [account.lykos.ai](https://account.lykos.ai) に一度ログインするだけで、Stability Matrixや他のLykos AIサービスに簡単にアクセスできます。
+* **より柔軟なログイン方法:** 既存の[lykos.ai](https://lykos.ai)アカウント、または**Apple**、**GitHub**、**Google**でのサインインが可能になりました。
+* **プライバシー保護の強化:** Stability Matrixは必要な権限のみ要求します。
+* **業界標準のセキュリティ:** 安全なログインの金標準であるOAuth 2.0を採用しています。
+* **将来を見据えた準備:** これにより今後も他のアプリやサービスと安全に連携できます。
+
+### 何をすればいいの?
+
+改めて"Settings"から、"Lykos account"の横にある"Connect"をクリックしてください。
+
+### 今後、"Lykos account" の登録が必須ということ?
+
+必須ではないです! Stability Matrixは"Lykos account"がなくてもお使い頂けます。"Lykos account"に登録連携することで追加機能を利用できます。現在はPatreonサポーター向けの開発ビルドの自動アップデートなどです。(今後も追加予定!)
+
+
+
- 設定に移動
+ 設定に行く
+
+
+ Prompt Amplifierを試そう! あなたのプロンプトを簡単にクオリティアップ!
- 有効
+ Enable
- 無効
+ Disable
- Lykosアカウントを接続
+ Lykos accountに接続
- 接続機能を使用するには、Lykosアカウントでサインインしてください。
+ この機能を使うには、Lykos accountにサインインしてください。
もう一度サインインしてください
- ログインの有効期限が切れました。続けるには、もう一度サインインしてください。
+ ログイン期限が切れました。もう一度サインインしてください。
- Stability Matrixへのご支援
+ Stability Matrixをサポートする
- いつもStability Matrixをご支援いただき、誠にありがとうございます。
+ Stability Matrixをサポートしてくれてありがとう!
- **{0}** のような機能は、サポーターの皆様への特典としてご提供しています。皆様のご支援のおかげで、サーバー費用を賄い、Stability Matrixの開発を継続できております。
+ **{0}**のような機能は、サポーターの皆様が利用できる特典です。Stability Matrixの開発とサーバー費用は、皆様のご支援により支えられております。
- **{0}** のような機能は、**{1}** サポーターレベル(またはそれ以上)からご利用いただけます。皆様のご支援のおかげで、高度な接続機能のサーバー費用を賄い、Stability Matrixの改善を続けることが可能となっております。
+ **{0}**のような機能は、サポーターレベル**{1}**以上の方が利用できる特典です。皆様のご厚情により、サーバー費用と、より高度な接続機能を維持しながら、Stability Matrixの改良を続けられております。
- すでにPatreonでご支援いただいている場合は、引き続き機能をご利用いただくためにアカウント連携をお願いいたします。
+ もしも既にPatreonで私たちをサポートしている場合は、アカウントをリンクしてください。
アカウント設定
- 支援オプションを表示
+ サポートオプションを見る
- 後で確認する
+ Maybe Later
- 状態
+ Status
- 有効
+ Active
- 無効(標準接続を使用中)
+ Inactive (標準接続を使用)
- Civitaiなどのオンラインリポジトリからモデルを閲覧する際に、より高速な検索結果を体験できます。
+ CivitAIなどのオンラインリポジトリからモデルを検索する際、より高速に取得できます。
- 第三者リポジトリ向けの実験的な最適化です。公式に提携しているものではありません。利用可能性は変動する場合があります。
+ 実験的なサードパーティリポジトリ用の最適化です。公式な提携はなく、利用可否は変動する可能性があります。
- ベータ版
+ Beta
- 加速モデル探索
+ モデル検索の高速化
- ### ✨ 新機能:Prompt Amplifier 登場
-
-実験的なSparkモデルを搭載した Lykos AI のアシスタントが、お客様のプロンプトを創造的に拡張したものを生成します。
-
-Prompt Amplifier は、安全なエンタープライズ級クラウド環境で実行されます — お客様のマシン上でローカルに実行されることは**ありません**。
-
-### ☁️ クラウドを選ぶ理由
+ ### 【🪄紹介】 Prompt Amplifier
+私達が開発している実験中のプロンプト専用AIモデル"Spark"が、あなたのプロンプトを増強します。
-Spark モデルは、数兆パラメータの基盤モデルに匹敵する規模で動作し、相当な計算能力を必要とします。私たちはローカルで実行可能な機能を最大限に活用することに尽力していますが、Spark の高度な機能は**今すぐ**当社のクラウドインフラストラクチャを通じて利用可能です。
+Prompt Amplifierはあなたのローカル環境を**使わず**、セキュアなエンタープライズグレードのクラウドサーバで稼働しています。
-### 🔒 プライバシー第一
+### ☁️ クラウドを使う理由は?
+Sparkは兆レベルのパラメータを持つ基盤モデルで、膨大なパワーが必要です。ローカル環境で動作可能な機能の最大化に努めていますが、"今のところ"Sparkの高度な機能はクラウドを通じて利用可能です。
-私たちはプライバシーを最優先します([Gen AI 利用規約](https://lykos.ai/gen-ai-terms))。お客様のプロンプトや出力は、Lykos AI または必要なクラウドインフラストラクチャパートナーによって、AI トレーニングに**決して**使用されることはありません。安全な処理は、お客様の拡張生成のためだけに行われます。**その後、プロンプトの内容自体ではなく、メタデータ(タイムスタンプやトークン数など)のみを保持します**。お客様のデータが販売されたり共有されたりすることは決してありません。
+### 🔒 プライバシーファースト
+私たちはプライバシーを最優先に考えています。([生成AI利用規約](https://lykos.ai/gen-ai-terms)) **あなたのプロンプトや出力内容は、Lykos AIや必要なクラウドインフラパートナーによるAI学習には一切使用されません。** 処理はお客様の質問に対する返答を生成するためだけに安全を重視して使われ、**その後はプロンプト内容そのものではなく、メタデータ(タイムスタンプやトークン数など)のみを保存します。** あなたのデータが販売や共有されることは決してありません。
-
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx
index efe3537fe..2d83d3c55 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.resx
@@ -1419,4 +1419,91 @@ The Spark model operates at a scale comparable to trillion-parameter foundation
### 🔒 Privacy First
We prioritize your privacy ([Gen AI Terms](<https://lykos.ai/gen-ai-terms>)). **Your prompts/outputs are NEVER used for AI training by Lykos AI or our necessary cloud infrastructure partners.** Secure processing occurs solely to generate your amplification, **after which we only retain metadata (like timestamps and token counts), not the prompt content itself.** Your data is never sold or shared.
+
+ Show Unsupported Python Versions
+
+
+ You may encounter problems with some packages when using unsupported Python versions
+
+
+ This will show all available Python versions, including those that are not supported by Stability Matrix. Are you sure?
+
+
+ Unsupported Python Versions
+
+
+ You must be logged in to download this checkpoint. Please enter a Hugging Face Access Token in the settings.
+
+
+ Login required to download this model
+
+
+ Enter package name
+
+
+ Package name cannot be empty
+
+
+ Enter a new name for '{0}'
+
+
+ Package named '{0}' already exists
+
+
+ Bulk Download Started
+
+
+ {0} files have started downloading. Check the Downloads tab for progress.
+
+
+ Download Started
+
+
+ {0} will be saved to {1}
+
+
+ Author
+
+
+ Hash
+
+
+ Last Updated
+
+
+ File Name Pattern
+
+
+ Files
+
+
+ Inference Defaults
+
+
+ When enabled, these settings will be applied automatically when this model is selected in the Inference tab
+
+
+ Show
+
+
+ Early Access Models
+
+
+ NSFW Content
+
+
+ Non-Model Files
+
+
+ Installed Models
+
+
+ Download All Files (All Versions)
+
+
+ View
+
+
+ Filter
+
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.uk-UA.resx b/StabilityMatrix.Avalonia/Languages/Resources.uk-UA.resx
new file mode 100644
index 000000000..7446a7a9d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Languages/Resources.uk-UA.resx
@@ -0,0 +1,1519 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Запустити
+
+
+ Вийти
+
+
+ Зберегти
+
+
+ Скасувати
+
+
+ Мова
+
+
+ Щобзастосувати нові налаштування мови потрібен перезапуск
+
+
+ Перезапустити
+
+
+ Перезапустити пізніше
+
+
+ Потрібен перезапуск
+
+
+ Невідомий Пакунок
+
+
+ Імпортувати
+
+
+ Тип пакунку
+
+
+ Версія
+
+
+ Тип версії
+
+
+ Релізи
+
+
+ Вітки
+
+
+ Щоб імпортувати чекпоїнти, перетягніть їх сюди
+
+
+ Акцент
+
+
+ Зменшення акценту
+
+
+ Ембедінґи / Текстова інверсія
+
+
+ Мережі (LoRA / LyCORIS)
+
+
+ Коментарі
+
+
+ Показувати сітку пікселів при наближенні
+
+
+ Кроки
+
+
+ Кроки - Основні
+
+
+ Кроки - Уточнювач
+
+
+ Масштаб CFG
+
+
+ Ступінь шумозаглушення
+
+
+ Ширина
+
+
+ Висота
+
+
+ Уточнювач
+
+
+ ВАЕ
+
+
+ Модель
+
+
+ Під'єднатися
+
+
+ Під'єднання...
+
+
+ Закрити
+
+
+ Очікування підключення...
+
+
+ Доступне оновлення
+
+
+ Підтримати на Patreon
+
+
+ Приєднатися до Discord-сервера
+
+
+ Завантаження
+
+
+ Встановити
+
+
+ Пропустити початкове налаштування
+
+
+ Виникла неочікувана помилка
+
+
+ Закрити програму
+
+
+ Відображуване ім’я
+
+
+ Інсталяція з цією назвою вже існує.
+
+
+ Будь ласка, оберіть іншу назву або локацію для встановлення.
+
+
+ Просунуті налаштування
+
+
+ Коміт
+
+
+ Стратегія спільної папки моделей
+
+
+ Версія PyTorch
+
+
+ Закрити після завершення
+
+
+ Папка даних
+
+
+ Тут буде розміщено дані програми (контрольні точки моделей, веб-інтерфейси тощо).
+
+
+ При роботі з дисками FAT32 або exFAT можливі помилки. Рекомендуємо обрати інший диск для більш стабільної роботи.
+
+
+ Портативний режим
+
+
+ У портативному режимі всі дані та налаштування зберігатимуться у тій же папці, що й програма. Ви зможете переміщувати програму разом із папкою «Data» в інше місце або на інший комп’ютер.
+
+
+ Продовжити
+
+
+ Попереднє зображення
+
+
+ Наступне зображення
+
+
+ Опис моделі
+
+
+ Доступна нова версія Stability Matrix!
+
+
+ Імпортувати останнє -
+
+
+ Всі версії
+
+
+ Шукати моделі, #теги чи @користувачів
+
+
+ Шукати
+
+
+ Сортування
+
+
+ Період
+
+
+ Тип моделі
+
+
+ Базова модель
+
+
+ Показувати контент 18+
+
+
+ Дані передано від CivitAI
+
+
+ Сторінка
+
+
+ Перша сторінка
+
+
+ Попередня сторінка
+
+
+ Наступна сторінка
+
+
+ Остання сторінка
+
+
+ Перейменувати
+
+
+ Видалити
+
+
+ Відкрити на CivitAI
+
+
+ Підключена модель
+
+
+ Локальна модель
+
+
+ Показати в файловому менеджері
+
+
+ Новий
+
+
+ Папка
+
+
+ Перетягніть файл сюди для імпорту
+
+
+ Імпортувати з метаданими
+
+
+ Шукати пов’язані метадані при новому локальному імпорті
+
+
+ Індексація...
+
+
+ Папка з моделями
+
+
+ Категорії
+
+
+ Почнімо
+
+
+ Я прочитав(-ла) та погоджуюся з
+
+
+ Ліцензійною Угодою.
+
+
+ Знайти пов'язані метадані
+
+
+ Показати зображення моделі
+
+
+ Зовнішній вигляд
+
+
+ Тема
+
+
+ Менеджер чекпоїнтів
+
+
+ Видаляти символічні посилання на спільну папку контрольних точок при завершенні роботи
+
+
+ Виберіть цю опцію, якщо у вас виникають проблеми з переміщенням Stability Matrix на інший диск
+
+
+ Скинути кеш контрольних точок
+
+
+ Перебудовує кеш встановлених контрольних точок. Використовуйте, якщо контрольні точки некоректно позначені у переглядачі моделей
+
+
+ Середовище пакету
+
+
+ Змінити
+
+
+ Змінні середовища
+
+
+ Вбудований Python
+
+
+ Перевірити версію
+
+
+ Інтеграції
+
+
+ Rich Presense з Discord
+
+
+ Система
+
+
+ Додати Stability Matrix до меню запуску
+
+
+ Використовує поточне розташування програми, ви можете запустити це знову, якщо перемістите програму
+
+
+ Доступно тільки на Windows -_-
+
+
+ Додати для поточного юзера
+
+
+ Додати для всіх юзерів
+
+
+ Обрати новий каталог даних
+
+
+ Не переміщує існуючі дані. Потребує перезапуску програми.
+
+
+ Обрати каталог
+
+
+ Про програму
+
+
+ Stability Matrix
+
+
+ Ліцензія та відкритий код
+
+
+ Натисніть Запустити щоб почати!
+
+
+ Зупинити
+
+
+ Надіслати введення
+
+
+ Введення
+
+
+ Надіслати
+
+
+ Необхідне введення
+
+
+ Підтвердити?
+
+
+ Так
+
+
+ Ні
+
+
+ Відкрити веб-інтерфейс
+
+
+ Вітаємо у Stability Matrix!
+
+
+ Оберіть бажаний інтерфейс, щоб почати
+
+
+ Йде інсталляція
+
+
+ Переходимо до сторінки запуску
+
+
+ Завантаження пакунка...
+
+
+ Завантаження завершене
+
+
+ Встановлення завершене
+
+
+ Інсталяція залежностей...
+
+
+ Встановлюємо залежності...
+
+
+ Відкрити в файловому провіднику
+
+
+ Відкрити в Finder
+
+
+ Видалити
+
+
+ Перевірити наявність оновлень
+
+
+ Оновити
+
+
+ Додати пакунок
+
+
+ Щоб почати, додайте пакунок!
+
+
+ Назва
+
+
+ Значення
+
+
+ Прибрати
+
+
+ Деталі
+
+
+ Стек викликів
+
+
+ Внутрішня помилка
+
+
+ Пошук...
+
+
+ ОК
+
+
+ Повторити
+
+
+ Інформація про версію Python
+
+
+ Перезапустити
+
+
+ Підтвердити видалення
+
+
+ Ця дія видалить папку пакунка разом із усіма файлами та зображеннями, які там є.
+
+
+ Видаляємо пакунок...
+
+
+ Пакунок видалено
+
+
+ Не вдалося видалити деякі файли. Переконайтеся, що всі файли у папці пакету закриті, і повторіть спробу.
+
+
+ Неправильний тип пакунка
+
+
+ Оновлюємо {0}
+
+
+ Оновлення завершене
+
+
+ {0} оновлено до останньої версії
+
+
+ Помилка під час оновлення {0}
+
+
+ Оновлення не вдалося
+
+
+ Відкрити в браузері
+
+
+ Помилка встановлення пакету
+
+
+ Гілка
+
+
+ Автоматично прокручувати до кінця консолі
+
+
+ Ліцензія
+
+
+ Обмін моделями
+
+
+ Будь ласка, оберіть папку з даними
+
+
+ Назва каталогу даних
+
+
+ Поточна директорія:
+
+
+ Програма перезапуститься після оновлення
+
+
+ Нагадати пізніше
+
+
+ Встановити зараз
+
+
+ Примітки до випуску
+
+
+ Відкрити проєкт...
+
+
+ Зберегти як...
+
+
+ Відновити макет за замовчуванням
+
+
+ Спільний вихідний каталог
+
+
+ Індекс партії
+
+
+ Копіювати
+
+
+ Відкрити у переглядачі зображень
+
+
+ Вибрано {0} зображень
+
+
+ Вихідний каталог
+
+
+ Вихідний тип
+
+
+ Очистити вибір
+
+
+ Вибрати все
+
+
+ Надіслати на обробку
+
+
+ Текст в зображення
+
+
+ Зображення в зображення
+
+
+ Інпейнтінг
+
+
+ Апскейл
+
+
+ Результати
+
+
+ Обрано 1 зображення
+
+
+ Пакунки Python
+
+
+ Об'єднати
+
+
+ Ви впевнені?
+
+
+ Ця дія перемістить усі згенеровані зображення з вибраних пакетів до папки “Consolidated” у спільному каталозі результатів. Цю операцію неможливо скасувати.
+
+
+ Оновити
+
+
+ Оновити
+
+
+ Повернути попередню версію
+
+
+ Відкрити на GitHub
+
+
+ З'єднано
+
+
+ Від'єднатися
+
+
+ Email
+
+
+ Юзернейм
+
+
+ Пароль
+
+
+ Увійти
+
+
+ Створити аккаунт
+
+
+ Підтвердити пароль
+
+
+ Ключ APi
+
+
+ Аккаунти
+
+
+ Препроцесор
+
+
+ Потужність
+
+
+ Контрольна вага
+
+
+ Контрольні кроки
+
+
+ Щоб завантажити цей чекпоінт, ви повинні увійти в систему. Будь ласка, введіть ключ API CivitAI в налаштуваннях.
+
+
+ Завантаження невдале
+
+
+ Автоматичні оновлення
+
+
+ Для ранніх користувачів. Попередні збірки будуть надійнішими, ніж у каналі Dev, і виходитимуть ближче до стабільних релізів. Ваші відгуки допоможуть нам швидше знаходити помилки та вдосконалювати елементи дизайну.
+
+
+ Для технічних користувачів. Отримуйте першими доступ до наших збірок для розробників з гілок функцій, щойно вони стануть доступними. У процесі експериментів з новими функціями можуть з'явитися деякі шорсткості та помилки.
+
+
+ Оновлення
+
+
+ Все оновлено
+
+
+ В останнє перевірено: {0}
+
+
+ Копіювати тріґерні слова
+
+
+ Слова-тріґери:
+
+
+ Додаткові папки, такі як IPAdapters та TextualInversions (вбудовування), можна ввімкнути тут
+
+
+ Відкрити на Hugging Face
+
+
+ Оновити існуючі метадані
+
+
+ Загальні
+ A general settings category
+
+
+ Генерація
+ The Inference feature page
+
+
+ Промпт
+ A settings category for Inference generation prompts
+
+
+ Вихідні файли зображень
+
+
+ Переглядач зображень
+
+
+ Підказки при введенні
+
+
+ Замінюйте нижні підкреслення на пробіли під час автозаповнення
+
+
+ Теги підказки
+ Tags for image generation prompts
+
+
+ Імпортувати теги підказки
+
+
+ Помічає файл для підказок при автодоповненні промпта (підтримується формат a1111-sd-webui-tagcomplete .csv)
+
+
+ Інформація про систему
+
+
+ CivitAI
+
+
+ Hugging Face
+
+
+ Аддони
+ Inference Sampler Addons
+
+
+ Зберегти проміжне зображення
+ Inference module step to save an intermediate image
+
+
+ Налаштування
+
+
+ Обрати файл
+
+
+ Замінити вміст
+
+
+ Поки що недоступно
+
+
+ Функція буде доступна в наступному оновленні
+
+
+ Відсутній файл зображення
+
+
+ Різдвяний режим
+
+
+ Пропуск CLIP
+
+
+ Зображення в відео
+
+
+ Кадри в секунду
+
+
+ Min CFG
+
+
+ Без втрат
+
+
+ Рамки
+
+
+ Motion Bucket ID
+
+
+ Рівень аугментації
+
+
+ Метод
+
+
+ Якість
+
+
+ Знайти в браузері моделей
+
+
+ Встановлено
+
+
+ Розширень не знайдено.
+
+
+ Приховати
+
+
+ Копіювати деталі
+
+
+ Завантажити
+
+
+ Перевіряйте прогргес завантажень тут.
+
+
+ Рекомендовані моделі
+
+
+ Поки ваш пакунок встановлюється, ось деякі моделі, які ми рекомендуємо для початку.
+
+
+ Сповіщення
+
+
+ Жодних
+
+
+ Потрібен ComfyUA
+
+
+ Щоб встовити цей пакунок потрібен ComfyUI. Хочете встановити зараз?
+
+
+ Будь ласка, оберіть директорію для завантаження.
+
+
+ Оберіть директорію для завантаження:
+
+
+ Конфіґ
+
+
+ Автоматично прокручувати до кінця
+
+
+ Підтвердити вихід
+
+
+ Ви впевнені, що хочете вийти? Це також закриє всі запущені пакети.
+
+
+ Консоль
+
+
+ Веб-інтерфейс
+
+
+ Пакунки
+
+
+ Цю дію не можна скасувати.
+
+
+ Ви впевнені, що хочете видалити {0} зображень?
+
+
+ Ми перевіряємо деякі специфікації обладнання, щоб визначити сумісність.
+
+
+ Все виглядає чудово!
+
+
+ Ми рекомендуємо використовувати відеокарту з підтримкою CUDA для найкращої роботи. Ви можете продовжувати без неї, але деякі пакети можуть не працювати, а генерація може бути повільнішим.
+
+
+ Чекпоїнти
+
+
+ Браузер моделей
+
+
+ Воркфлоу
+
+
+ Нескінченна прокрутка
+
+
+ Браузер воркфлоу
+
+
+ Відкрити на OpenArt
+
+
+ Деталі вузла
+
+
+ Опис робочого процесу
+
+
+ OpenArt Browser
+
+
+ Попередній перегляд Препроцесор
+
+
+ Кнопка "Відкрити веб-інтерфейс" перемістилася в командну панель
+
+
+ Інший екземпляр Stability Matrix вже запущено. Будь ласка, закрийте його, перш ніж запускати новий.
+
+
+ Матриця стабільності вже працює
+
+
+ {0} видалено успішно
+
+
+ Робочий процес Видалено
+
+
+ Виправлення помилок у робочих процесах
+
+
+ Встановлені робочі процеси
+
+
+ Імпортований робочий процес
+
+
+ Завершено імпорт робочих процесів та користувацьких вузлів
+
+
+ Робочий процес і кастомні вузли були імпортовані.
+
+
+ Натисніть тут, щоб переглянути синтаксис підказки і те, як включити Lora / Embeddings.
+
+
+ Додаткові мережі (Lora / LyCORIS)
+
+
+ Сила CLIP
+
+
+ Формат чисел
+
+
+ Ви збираєтеся видалити наступні елементи:
+
+
+ Ви збираєтеся видалити наступні {0} елементів:
+
+
+ Видалити назавжди
+
+
+ Перемістити в кошик
+
+
+ Ви впевнені, що хочете видалити стільки моделей: {0}?
+
+
+ Автоматичний пошук при завантаженні
+
+
+ Автоматично запускати пошук при завантаженні сторінки браузера моделі
+
+
+ Переключити видимість
+
+
+ Маска для обрізання
+
+
+ Папки додатків
+
+
+ Логи
+
+
+ Дані програми
+
+
+ {0} оновлено до вибраної версії
+
+
+ Копіювати як растрове зображення
+
+
+ Вимкнути перевірку оновлень
+
+
+ Будь ласка, розпакуйте додаток з ZIP-архіву перед запуском Stability Matrix
+
+
+ Розмір історії
+
+
+ Кількість рядків над тими, що відображаються в консолі, до яких можна прокрутити назад
+
+
+ У нас виникли проблеми з підключенням вашого облікового запису
+
+
+ Редагування метаданих моделі
+
+
+ NSFW
+
+
+ Теги
+
+
+ Назва версії
+
+
+ Треновані слова
+
+
+ Попередній перегляд зображення
+
+
+ Розмір партії
+
+
+ Партії
+
+
+ Семплер
+
+
+ Планувальник
+
+
+ Максимальний розмір
+
+
+ Використовувати окремий промпт
+
+
+ Зернятко
+
+
+ Негативний промпт
+
+
+ Нова папка
+
+
+ Копіювати посилання в буфер обміну
+
+
+ Увійдіть через {0}
+ e.g. 'Sign in with Google'
+
+
+ Будь ласка, дозвольте вашому браузеру відкрити цей додаток, коли з'явиться запит на продовження.
+
+
+ Відкрийте посилання у вашому браузері та дотримуйтесь інструкцій, щоб підключити свій обліковий запис.
+
+
+ Перевизначення залежностей Python
+
+
+ Додавання, заміна або видалення залежностей для встановлення та оновлення
+
+
+ Специфікатори залежностей
+
+
+ {
+ "packageName": "stable-diffusion-webui",
+ "packageVersion": "v1.10.0",
+ "isSuccess": true,
+ "type": "install",
+ "timestamp": "2024-09-04T02:14:04.1967404+00:00"
+}
+
+
+ Аналітика
+
+
+ Ви завжди можете змінити цю поведінку в {0}.
+ e.g. 'You can always change this behavior in Settings > Category > Item.'
+
+
+ Допоможіть нам покращити Stability Matrix, надіславши анонімні дані про використовувані функції, версії операційної системи, типи встановлених пакунків тощо. Надіслані дані ніколи не будуть пов'язані з вами або вашим обліковим записом і не міститимуть особистих даних або будь-якої конфіденційної інформації.
+
+
+ Допоможіть нам покращити Stability Matrix, надіславши анонімні дані про використовувані функції, версії операційної системи, типи встановлених пакунків тощо.
+
+
+ Надіслані дані ніколи не будуть пов'язані з вами або вашим обліковим записом і не будуть містити особистих даних або будь-якої конфіденційної інформації.
+
+
+ Дані про використання
+
+
+ Політика конфіденційності
+
+
+ Приховане зображення
+
+
+ Зображення не знайдено
+
+
+ Приховати порожні категорії
+
+
+ Показати зображення NSFW
+
+
+ Увімкнути довгі шляхи
+ (Setting to enable long file paths on windows)
+
+
+ Видалити обмеження MAX_PATH зі звичайних функцій файлів і каталогів Win32
+ (Setting to enable long file paths on windows)
+
+
+ Налаштування системи
+
+
+ Застосовані зміни
+
+
+ Для того, щоб зміни в системі набули чинності, може знадобитися перезавантаження.
+
+
+ Випуски для цього пакунка недоступні.
+
+
+ Будь ласка, повідомте нам про цю проблему, вказавши деталі нижче, і прикріпіть заархівовані файли журналів.
+
+
+ Ви можете продовжити роботу, але повна функціональність буде доступна після перезапуску. Будь ласка, повідомте нам про цю проблему, вказавши деталі нижче, і прикріпіть заархівовані файли журналів.
+
+
+ Показати лог у файловому провіднику
+
+
+ Показати лог у Finder
+
+
+ Пакети розширення
+
+
+ Розширення не знайдено
+
+
+ Відкрити папку з пакетами розширень
+
+
+ Інсталяція пакета розширень
+
+
+ Додати до існуючого пакету
+
+
+ Новий пакет розширень
+
+
+ вЩоб створити його, просто виберіть потрібні розширення у вкладці "Доступні розширення" або "Встановлені розширення" та натисніть "Зберегти"
+
+
+ Додайте файл пакету розширень .json до теки ExtensionPacks у вашому каталозі Data.
+
+
+ - або -
+
+
+ Відкрити на OpenModelDB
+
+
+ Підстановочні знаки
+
+
+ Відкрийте посилання у вашому браузері та введіть наступний код для авторизації вашого облікового запису в Stability Matrix.
+
+
+ Скопіювати та відкрити
+
+
+ ## Увага: вихід з облікового запису Lykos та оновлення системи безпеки
+
+Ми внесли деякі важливі покращення в те, як Stability Matrix працює з акаунтами Lykos, оновивши їх до більш безпечної та зручної системи входу **(OAuth 2.0 з OpenID Connect)**. У зв'язку з цим ви вийшли зі свого облікового запису Lykos.
+
+### Чому відбулися зміни?
+
+Ваша безпека і конфіденційність важливі для нас. Це оновлення принесе:
+
+* **Спрощений досвід:** Увійдіть один раз на [account.lykos.ai](https://account.lykos.ai), щоб підключитися до Stability Matrix та інших сервісів Lykos AI.
+* **Більше способів входу:** Використовуйте свій існуючий обліковий запис [lykos.ai](https://lykos.ai) або увійдіть за допомогою **Apple**, **GitHub** або **Google**.
+* **Покращена конфіденційність:** Stability Matrix запитує лише необхідні дозволи.
+* **Безпека за галузевим стандартом:** Ми використовуємо OAuth 2.0, золотий стандарт для безпечних входів.
+* **Готовність до майбутнього:** Це забезпечує безпечне з'єднання з іншими програмами та службами.
+
+### Що мені потрібно зробити?
+
+Натисніть кнопку **"Перейти до налаштувань "**, потім натисніть **"Підключитися "** поруч з **"Обліковим записом Lykos "**.
+
+### Чи потрібен обліковий запис Lykos?
+
+Ні! Stability Matrix повноцінно функціонує і без нього. Але ваш обліковий запис Lykos дозволяє використовувати деякі додаткові підключені функції, такі як автоматичне оновлення збірок розробки для наших підписників Patreon (і багато іншого!).
+
+
+
+ Перейти до Налаштувань
+
+
+ Спробуйте новий підсилювач промпту! Покращуйте свої промпти для кращих результатів!
+
+
+ Увімкнути
+
+
+ Вимкнути
+
+
+ Підключіть свій обліковий запис Lykos
+
+
+ Увійдіть у свій обліковий запис Lykos, щоб користуватися функціями підключення.
+
+
+ Будь ласка, увійдіть ще раз
+
+
+ Ваш логін закінчився. Будь ласка, увійдіть знову, щоб продовжити.
+
+
+ Підтримка Stability Matrix
+
+
+ Дякуємо, що підтримуєте Stability Matrix!
+
+
+ Такі функції, як **{0}**, є однією з багатьох переваг, доступних для наших донатерів. Ваш внесок допомагає нам покривати витрати на сервер і підтримує розвиток Stability Matrix.
+
+
+ Такі функції, як **{0}**, доступні на рівні **{1}** (або вище). Ваш внесок допомагає нам покривати витрати на сервер для більш просунутих підключених функцій і дозволяє нам продовжувати покращувати Stability Matrix для всіх.
+
+
+ Якщо ви вже підтримуєте нас на Patreon, будь ласка, прив'яжіть свій акаунт, щоб продовжити.
+
+
+ Налаштування облікового запису
+
+
+ Переглянути параметри підтримки
+
+
+ Може, пізніше
+
+
+ Статус
+
+
+ Активний
+
+
+ Неактивний (Використовує стандартне з'єднання)
+
+
+ Скористайтеся швидшими результатами пошуку, переглядаючи моделі з онлайн-репозиторіїв, таких як CivitAI.
+
+
+ Експериментальна оптимізація для сторонніх репозиторіїв. Офіційно не пов'язана з нами; доступність може змінюватися.
+
+
+ Бета
+
+
+ Прискорене відкриття моделі
+
+
+ ### Представляємо: Підсилювач реплік
+Наш помічник зі штучним інтелектом, заснований на експериментальній моделі Spark, генерує творчі варіанти ваших підказок.
+
+Prompt Amplifier працює в нашому захищеному хмарному середовищі корпоративного рівня - він не працює локально на вашому комп'ютері.
+
+### ☁️️ Чому хмарне середовище?
+Модель Spark працює в масштабі, порівнянному з фундаментальними моделями з трильйонами параметрів, що вимагає значних обчислювальних потужностей. Хоча ми прагнемо максимізувати можливості локального запуску, розширені можливості Spark доступні **тепер** через нашу хмарну інфраструктуру.
+
+### Конфіденційність понад усе
+Ми ставимо на перше місце вашу конфіденційність ([Умови Gen AI] (<https://lykos.ai/gen-ai-terms>)). **Ваші підказки/вихідні дані НІКОЛИ не використовуються для навчання ШІ компанією Lykos AI або нашими партнерами з хмарної інфраструктури.** Безпечна обробка відбувається виключно для генерації вашого посилення, після чого ми зберігаємо лише метадані (наприклад, часові мітки та кількість токенів), а не сам вміст підказок.** Ваші дані ніколи не продаються та не передаються іншим особам.
+
+
+ Показати непідтримувані версії Python
+
+
+ При використанні непідтримуваних версій Python можуть виникнути проблеми з деякими пакунками
+
+
+ Буде показано всі доступні версії Python, включно з тими, які не підтримуються Stability Matrix. Ви впевнені?
+
+
+ Непідтримувані версії Python
+
+
+ Ви повинні увійти, щоб завантажити цей чекпоїнт. Будь ласка, додайте токен Hugging Face в налаштуваннях.
+
+
+ Для завантаження цієї моделі потрібен логін
+
+
+ Введіть назву пакета
+
+
+ Назва пакунка не може бути порожньою
+
+
+ Введіть нове ім'я для '{0}'
+
+
+ Пакунок з назвою '{0}' вже існує
+
+
+ Розпочато масове завантаження
+
+
+ {0} файли почали завантажуватися. Перевірте прогрес на вкладці Завантаження.
+
+
+ Завантаження розпочато
+
+
+ {0} буде збережено в {1}
+
+
+ Автор
+
+
+ Хеш
+
+
+ Останнє оновлення
+
+
+ Шаблон імен файлів
+
+
+ Файли
+
+
+ Висновок за замовчуванням
+
+
+ Якщо увімкнено, ці налаштування будуть застосовані автоматично, коли цю модель буде обрано на вкладці Висновки
+
+
+ Показати
+
+
+ Моделі раннього доступу
+
+
+ Контент 18+
+
+
+ Файли, що не є моделями
+
+
+ Встановлені моделі
+
+
+ Завантажити всі файли (всі версії)
+
+
+ Вигляд
+
+
+ Фільтр
+
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Models/HuggingFace/HuggingfaceItem.cs b/StabilityMatrix.Avalonia/Models/HuggingFace/HuggingfaceItem.cs
index 95e029056..3b742204a 100644
--- a/StabilityMatrix.Avalonia/Models/HuggingFace/HuggingfaceItem.cs
+++ b/StabilityMatrix.Avalonia/Models/HuggingFace/HuggingfaceItem.cs
@@ -9,4 +9,5 @@ public class HuggingfaceItem
public required string LicenseType { get; set; }
public string? LicensePath { get; set; }
public string? Subfolder { get; set; }
+ public bool LoginRequired { get; set; }
}
diff --git a/StabilityMatrix.Avalonia/Models/Inference/FileNameFormat.cs b/StabilityMatrix.Avalonia/Models/Inference/FileNameFormat.cs
index 5017ab386..f4caa98dd 100644
--- a/StabilityMatrix.Avalonia/Models/Inference/FileNameFormat.cs
+++ b/StabilityMatrix.Avalonia/Models/Inference/FileNameFormat.cs
@@ -37,9 +37,7 @@ public string GetFileName()
return Prefix
+ string.Join(
"",
- Parts.Select(
- part => part.Match(constant => constant, substitution => substitution.Invoke())
- )
+ Parts.Select(part => part.Match(constant => constant, substitution => substitution.Invoke()))
)
+ Postfix;
}
@@ -69,4 +67,5 @@ public static bool TryParse(
}
public const string DefaultTemplate = "{date}_{time}-{model_name}-{seed}";
+ public const string DefaultModelBrowserTemplate = "{file_name}";
}
diff --git a/StabilityMatrix.Avalonia/Models/Inference/FileNameFormatProvider.cs b/StabilityMatrix.Avalonia/Models/Inference/FileNameFormatProvider.cs
index 19f90e1b6..73bc2a2d4 100644
--- a/StabilityMatrix.Avalonia/Models/Inference/FileNameFormatProvider.cs
+++ b/StabilityMatrix.Avalonia/Models/Inference/FileNameFormatProvider.cs
@@ -8,6 +8,7 @@
using Avalonia.Data;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
+using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Inference;
namespace StabilityMatrix.Avalonia.Models.Inference;
@@ -20,6 +21,10 @@ public partial class FileNameFormatProvider
public string? ProjectName { get; init; }
+ public CivitModel? CivitModel { get; init; }
+ public CivitModelVersion? CivitModelVersion { get; init; }
+ public CivitFile? CivitFile { get; init; }
+
private Dictionary>? _substitutions;
public Dictionary> Substitutions =>
@@ -28,7 +33,10 @@ public partial class FileNameFormatProvider
{ "seed", () => GenerationParameters?.Seed.ToString() },
{ "prompt", () => GenerationParameters?.PositivePrompt },
{ "negative_prompt", () => GenerationParameters?.NegativePrompt },
- { "model_name", () => Path.GetFileNameWithoutExtension(GenerationParameters?.ModelName) },
+ {
+ "model_name",
+ () => Path.GetFileNameWithoutExtension(GenerationParameters?.ModelName) ?? CivitModel?.Name
+ },
{ "model_hash", () => GenerationParameters?.ModelHash },
{ "sampler", () => GenerationParameters?.Sampler },
{ "cfgscale", () => GenerationParameters?.CfgScale.ToString() },
@@ -38,7 +46,15 @@ public partial class FileNameFormatProvider
{ "project_type", () => ProjectType?.GetStringValue() },
{ "project_name", () => ProjectName },
{ "date", () => DateTime.Now.ToString("yyyy-MM-dd") },
- { "time", () => DateTime.Now.ToString("HH-mm-ss") }
+ { "time", () => DateTime.Now.ToString("HH-mm-ss") },
+ { "author", () => CivitModel?.Creator.Username },
+ { "base_model", () => CivitModelVersion?.BaseModel },
+ { "file_name", () => Path.GetFileNameWithoutExtension(CivitFile?.Name) },
+ { "file_id", () => CivitFile?.Id.ToString() },
+ { "model_id", () => CivitModel?.Id.ToString() },
+ { "model_version_id", () => CivitModelVersion?.Id.ToString() },
+ { "model_version_name", () => CivitModelVersion?.Name },
+ { "model_type", () => CivitModel?.Type.ToString() },
};
///
@@ -151,7 +167,34 @@ public static FileNameFormatProvider GetSample()
{
GenerationParameters = GenerationParameters.GetSample(),
ProjectType = InferenceProjectType.TextToImage,
- ProjectName = "Sample Project"
+ ProjectName = "Sample Project",
+ };
+ }
+
+ public static FileNameFormatProvider GetSampleForModelBrowser()
+ {
+ return new FileNameFormatProvider
+ {
+ CivitModel = new CivitModel
+ {
+ Id = 1234,
+ Name = "Sample Model",
+ Creator = new CivitCreator { Username = "SampleUser" },
+ Type = CivitModelType.Checkpoint,
+ },
+ CivitModelVersion = new CivitModelVersion
+ {
+ Id = 5678,
+ Name = "v1.0",
+ BaseModel = "Illustrious",
+ },
+ CivitFile = new CivitFile
+ {
+ Id = 910,
+ Name = "sample_file.ckpt",
+ Type = CivitFileType.Model,
+ Metadata = new CivitFileMetadata { Size = "pruned" },
+ },
};
}
diff --git a/StabilityMatrix.Avalonia/Models/TreeViewDirectory.cs b/StabilityMatrix.Avalonia/Models/TreeViewDirectory.cs
index ebf74710c..2614ff673 100644
--- a/StabilityMatrix.Avalonia/Models/TreeViewDirectory.cs
+++ b/StabilityMatrix.Avalonia/Models/TreeViewDirectory.cs
@@ -8,4 +8,7 @@ public partial class TreeViewDirectory : ObservableObject
public ObservableCollection SubDirectories { get; set; } = new();
public required string Name { get; set; }
public required string Path { get; set; }
+
+ [ObservableProperty]
+ public partial bool IsExpanded { get; set; }
}
diff --git a/StabilityMatrix.Avalonia/Services/AccountsService.cs b/StabilityMatrix.Avalonia/Services/AccountsService.cs
index 3020500fd..4bd4a61e3 100644
--- a/StabilityMatrix.Avalonia/Services/AccountsService.cs
+++ b/StabilityMatrix.Avalonia/Services/AccountsService.cs
@@ -31,6 +31,7 @@ public class AccountsService : IAccountsService
private readonly ILykosAuthApiV1 lykosAuthApi;
private readonly ILykosAuthApiV2 lykosAuthApiV2;
private readonly ICivitTRPCApi civitTRPCApi;
+ private readonly IHuggingFaceApi huggingFaceApi; // Added
private readonly OpenIddictClientService openIdClient;
///
@@ -39,16 +40,22 @@ public class AccountsService : IAccountsService
///
public event EventHandler? CivitAccountStatusUpdate;
+ ///
+ public event EventHandler? HuggingFaceAccountStatusUpdate;
+
public LykosAccountStatusUpdateEventArgs? LykosStatus { get; private set; }
public CivitAccountStatusUpdateEventArgs? CivitStatus { get; private set; }
+ public HuggingFaceAccountStatusUpdateEventArgs? HuggingFaceStatus { get; private set; }
+
public AccountsService(
ILogger logger,
ISecretsManager secretsManager,
ILykosAuthApiV1 lykosAuthApi,
ILykosAuthApiV2 lykosAuthApiV2,
ICivitTRPCApi civitTRPCApi,
+ IHuggingFaceApi huggingFaceApi, // Added
OpenIddictClientService openIdClient
)
{
@@ -57,10 +64,13 @@ OpenIddictClientService openIdClient
this.lykosAuthApi = lykosAuthApi;
this.lykosAuthApiV2 = lykosAuthApiV2;
this.civitTRPCApi = civitTRPCApi;
+ this.huggingFaceApi = huggingFaceApi; // Added
this.openIdClient = openIdClient;
// Update our own status when the Lykos account status changes
LykosAccountStatusUpdate += (_, args) => LykosStatus = args;
+ CivitAccountStatusUpdate += (_, args) => CivitStatus = args; // Assuming this was intended
+ HuggingFaceAccountStatusUpdate += (_, args) => HuggingFaceStatus = args;
}
public async Task HasStoredLykosAccountAsync()
@@ -191,6 +201,7 @@ public async Task RefreshAsync()
await RefreshLykosAsync(secrets);
await RefreshCivitAsync(secrets);
+ await RefreshHuggingFaceAsync(secrets);
}
public async Task RefreshLykosAsync()
@@ -200,6 +211,12 @@ public async Task RefreshLykosAsync()
await RefreshLykosAsync(secrets);
}
+ public async Task RefreshHuggingFaceAsync()
+ {
+ var secrets = await secretsManager.SafeLoadAsync();
+ await RefreshHuggingFaceAsync(secrets);
+ }
+
private async Task RefreshLykosAsync(Secrets secrets)
{
if (
@@ -255,6 +272,45 @@ secrets.LykosAccountV2 is not null
OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs.Disconnected);
}
+ private async Task RefreshHuggingFaceAsync(Secrets secrets)
+ {
+ if (!string.IsNullOrWhiteSpace(secrets.HuggingFaceToken))
+ {
+ try
+ {
+ var response = await huggingFaceApi.GetCurrentUserAsync($"Bearer {secrets.HuggingFaceToken}");
+ if (response.IsSuccessStatusCode && response.Content != null)
+ {
+ // Token is valid, user info fetched
+ logger.LogInformation("Hugging Face token is valid. User: {Username}", response.Content.Name);
+ OnHuggingFaceAccountStatusUpdate(new HuggingFaceAccountStatusUpdateEventArgs(true, response.Content.Name));
+ }
+ else
+ {
+ // Token is likely invalid or other API error
+ logger.LogWarning("Hugging Face token validation failed. Status: {StatusCode}, Error: {Error}, Content: {Content}", response.StatusCode, response.Error?.ToString(), await response.Error?.GetContentAsAsync() ?? "N/A");
+ OnHuggingFaceAccountStatusUpdate(new HuggingFaceAccountStatusUpdateEventArgs(false, null, $"Token validation failed: {response.StatusCode}"));
+ }
+ }
+ catch (ApiException apiEx)
+ {
+ // Handle Refit's ApiException (network issues, non-success status codes not caught by IsSuccessStatusCode if IApiResponse isn't used directly)
+ logger.LogError(apiEx, "Hugging Face API request failed during token validation. Content: {Content}", await apiEx.GetContentAsAsync() ?? "N/A");
+ OnHuggingFaceAccountStatusUpdate(new HuggingFaceAccountStatusUpdateEventArgs(false, null, "API request failed during token validation."));
+ }
+ catch (Exception ex)
+ {
+ // Handle other unexpected errors
+ logger.LogError(ex, "An unexpected error occurred during Hugging Face token validation.");
+ OnHuggingFaceAccountStatusUpdate(new HuggingFaceAccountStatusUpdateEventArgs(false, null, "An unexpected error occurred."));
+ }
+ }
+ else
+ {
+ OnHuggingFaceAccountStatusUpdate(HuggingFaceAccountStatusUpdateEventArgs.Disconnected);
+ }
+ }
+
private async Task RefreshCivitAsync(Secrets secrets)
{
if (secrets.CivitApi is not null)
@@ -324,4 +380,37 @@ private void OnCivitAccountStatusUpdate(CivitAccountStatusUpdateEventArgs e)
CivitAccountStatusUpdate?.Invoke(this, e);
}
+
+ private void OnHuggingFaceAccountStatusUpdate(HuggingFaceAccountStatusUpdateEventArgs e)
+ {
+ if (!e.IsConnected && HuggingFaceStatus?.IsConnected == true)
+ {
+ logger.LogInformation("Hugging Face account disconnected");
+ }
+ else if (e.IsConnected && HuggingFaceStatus?.IsConnected == false)
+ {
+ // Assuming Username might be null for now as we are not fetching it.
+ logger.LogInformation("Hugging Face account connected" + (string.IsNullOrWhiteSpace(e.Username) ? "" : $" (User: {e.Username})"));
+ }
+ else if (!e.IsConnected && !string.IsNullOrWhiteSpace(e.ErrorMessage))
+ {
+ logger.LogWarning("Hugging Face connection/validation failed: {ErrorMessage}", e.ErrorMessage);
+ }
+ HuggingFaceAccountStatusUpdate?.Invoke(this, e);
+ }
+
+ public async Task HuggingFaceLoginAsync(string token)
+ {
+ var secrets = await secretsManager.SafeLoadAsync();
+ secrets = secrets with { HuggingFaceToken = token };
+ await secretsManager.SaveAsync(secrets);
+ await RefreshHuggingFaceAsync(secrets);
+ }
+
+ public async Task HuggingFaceLogoutAsync()
+ {
+ var secrets = await secretsManager.SafeLoadAsync();
+ await secretsManager.SaveAsync(secrets with { HuggingFaceToken = null });
+ OnHuggingFaceAccountStatusUpdate(HuggingFaceAccountStatusUpdateEventArgs.Disconnected);
+ }
}
diff --git a/StabilityMatrix.Avalonia/Services/IAccountsService.cs b/StabilityMatrix.Avalonia/Services/IAccountsService.cs
index 6b888cea8..407fd4141 100644
--- a/StabilityMatrix.Avalonia/Services/IAccountsService.cs
+++ b/StabilityMatrix.Avalonia/Services/IAccountsService.cs
@@ -1,15 +1,19 @@
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Api.Lykos;
+// Ensure this using is present if HuggingFaceAccountStatusUpdateEventArgs is in StabilityMatrix.Core.Models.Api
+// using StabilityMatrix.Core.Models.Api;
namespace StabilityMatrix.Avalonia.Services;
public interface IAccountsService
{
event EventHandler? LykosAccountStatusUpdate;
-
event EventHandler? CivitAccountStatusUpdate;
+ event EventHandler? HuggingFaceAccountStatusUpdate;
LykosAccountStatusUpdateEventArgs? LykosStatus { get; }
+ CivitAccountStatusUpdateEventArgs? CivitStatus { get; } // Assuming this was missed in the provided file content but is standard
+ HuggingFaceAccountStatusUpdateEventArgs? HuggingFaceStatus { get; }
///
/// Returns whether SecretsManager has a stored Lykos V2 account.
@@ -42,4 +46,8 @@ public interface IAccountsService
Task RefreshAsync();
Task RefreshLykosAsync();
+
+ Task HuggingFaceLoginAsync(string token);
+ Task HuggingFaceLogoutAsync();
+ Task RefreshHuggingFaceAsync();
}
diff --git a/StabilityMatrix.Avalonia/Services/IModelImportService.cs b/StabilityMatrix.Avalonia/Services/IModelImportService.cs
index 84cd53e60..190d36815 100644
--- a/StabilityMatrix.Avalonia/Services/IModelImportService.cs
+++ b/StabilityMatrix.Avalonia/Services/IModelImportService.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Api.OpenModelsDb;
@@ -24,6 +25,8 @@ Task DoImport(
DirectoryPath downloadFolder,
CivitModelVersion? selectedVersion = null,
CivitFile? selectedFile = null,
+ string? fileNameOverride = null,
+ SamplerCardViewModel? inferenceDefaults = null,
IProgress? progress = null,
Func? onImportComplete = null,
Func? onImportCanceled = null,
diff --git a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
index 5f03a3fd2..4804b7b3e 100644
--- a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
+++ b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
@@ -171,13 +171,8 @@ ICompletionProvider completionProvider
modelsSource
.Connect()
- .SortBy(
- f => f.ShortDisplayName,
- SortDirection.Ascending,
- SortOptimisations.ComparesImmutableValuesOnly
- )
.DeferUntilLoaded()
- .Bind(Models)
+ .SortAndBind(Models, SortExpressionComparer.Ascending(f => f.ShortDisplayName))
.ObserveOn(SynchronizationContext.Current)
.Subscribe();
@@ -251,13 +246,11 @@ ICompletionProvider completionProvider
unetModelsSource
.Connect()
- .SortBy(
- f => f.ShortDisplayName,
- SortDirection.Ascending,
- SortOptimisations.ComparesImmutableValuesOnly
- )
.DeferUntilLoaded()
- .Bind(UnetModels)
+ .SortAndBind(
+ UnetModels,
+ SortExpressionComparer.Ascending(f => f.ShortDisplayName)
+ )
.ObserveOn(SynchronizationContext.Current)
.Subscribe();
@@ -364,7 +357,10 @@ protected virtual async Task LoadSharedPropertiesAsync()
// Get model names
if (await Client.GetModelNamesAsync() is { } modelNames)
{
- modelsSource.EditDiff(modelNames.Select(HybridModelFile.FromRemote), HybridModelFile.Comparer);
+ modelsSource.EditDiff(
+ modelNames.Select(HybridModelFile.FromRemote),
+ HybridModelFile.RemoteLocalComparer
+ );
}
// Get control net model names
@@ -375,7 +371,7 @@ await Client.GetNodeOptionNamesAsync("ControlNetLoader", "control_net_name") is
{
controlNetModelsSource.EditDiff(
controlNetModelNames.Select(HybridModelFile.FromRemote),
- HybridModelFile.Comparer
+ HybridModelFile.RemoteLocalComparer
);
}
@@ -384,7 +380,7 @@ await Client.GetNodeOptionNamesAsync("ControlNetLoader", "control_net_name") is
{
loraModelsSource.EditDiff(
loraModelNames.Select(HybridModelFile.FromRemote),
- HybridModelFile.Comparer
+ HybridModelFile.RemoteLocalComparer
);
}
@@ -399,7 +395,7 @@ await Client.GetOptionalNodeOptionNamesAsync("UltralyticsDetectorProvider", "mod
HybridModelFile.None,
.. ultralyticsModelNames.Select(HybridModelFile.FromRemote),
];
- ultralyticsModelsSource.EditDiff(models, HybridModelFile.Comparer);
+ ultralyticsModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);
}
// Get SAM model names
@@ -410,7 +406,7 @@ .. ultralyticsModelNames.Select(HybridModelFile.FromRemote),
HybridModelFile.None,
.. samModelNames.Select(HybridModelFile.FromRemote),
];
- samModelsSource.EditDiff(models, HybridModelFile.Comparer);
+ samModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);
}
// Prompt Expansion indexing is local only
@@ -487,7 +483,7 @@ await Client.GetRequiredNodeOptionNamesFromOptionalNodeAsync("UnetLoaderGGUF", "
unetModels = unetModels.Concat(ggufModelNames.Select(HybridModelFile.FromRemote));
}
- unetModelsSource.AddOrUpdate(unetModels, HybridModelFile.Comparer);
+ unetModelsSource.AddOrUpdate(unetModels, HybridModelFile.RemoteLocalComparer);
}
// Get CLIP model names from DualCLIPLoader node
@@ -498,7 +494,19 @@ await Client.GetRequiredNodeOptionNamesFromOptionalNodeAsync("UnetLoaderGGUF", "
HybridModelFile.None,
.. clipModelNames.Select(HybridModelFile.FromRemote),
];
- clipModelsSource.EditDiff(models, HybridModelFile.Comparer);
+
+ if (
+ await Client.GetRequiredNodeOptionNamesFromOptionalNodeAsync(
+ "DualCLIPLoaderGGUF",
+ "clip_name1"
+ ) is
+ { } ggufClipModelNames
+ )
+ {
+ models = models.Concat(ggufClipModelNames.Select(HybridModelFile.FromRemote));
+ }
+
+ clipModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);
}
// Get CLIP Vision model names from CLIPVisionLoader node
@@ -509,7 +517,7 @@ .. clipModelNames.Select(HybridModelFile.FromRemote),
HybridModelFile.None,
.. clipVisionModelNames.Select(HybridModelFile.FromRemote),
];
- clipVisionModelsSource.EditDiff(models, HybridModelFile.Comparer);
+ clipVisionModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);
}
}
diff --git a/StabilityMatrix.Avalonia/Services/ModelImportService.cs b/StabilityMatrix.Avalonia/Services/ModelImportService.cs
index 80983bf98..7d36c2b9a 100644
--- a/StabilityMatrix.Avalonia/Services/ModelImportService.cs
+++ b/StabilityMatrix.Avalonia/Services/ModelImportService.cs
@@ -1,14 +1,7 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using AsyncAwaitBestPractices;
+using AsyncAwaitBestPractices;
using Avalonia.Controls.Notifications;
using Injectio.Attributes;
-using Python.Runtime;
-using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Api.OpenModelsDb;
@@ -30,11 +23,33 @@ public static async Task SaveCmInfo(
CivitModel model,
CivitModelVersion modelVersion,
CivitFile modelFile,
- DirectoryPath downloadDirectory
+ DirectoryPath downloadDirectory,
+ string? fileNameOverride = null,
+ SamplerCardViewModel? samplerCardVm = null
)
{
- var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Name);
- var modelInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTime.UtcNow);
+ var modelFileName = fileNameOverride ?? Path.GetFileNameWithoutExtension(modelFile.Name);
+ InferenceDefaults? inferenceDefaults = null;
+ if (samplerCardVm != null)
+ {
+ inferenceDefaults = new InferenceDefaults
+ {
+ Sampler = samplerCardVm.SelectedSampler,
+ Scheduler = samplerCardVm.SelectedScheduler,
+ CfgScale = samplerCardVm.CfgScale,
+ Steps = samplerCardVm.Steps,
+ Width = samplerCardVm.Width,
+ Height = samplerCardVm.Height,
+ };
+ }
+
+ var modelInfo = new ConnectedModelInfo(
+ model,
+ modelVersion,
+ modelFile,
+ DateTime.UtcNow,
+ inferenceDefaults
+ );
await modelInfo.SaveJsonToDirectory(downloadDirectory, modelFileName);
@@ -81,6 +96,8 @@ public async Task DoImport(
DirectoryPath downloadFolder,
CivitModelVersion? selectedVersion = null,
CivitFile? selectedFile = null,
+ string? fileNameOverride = null,
+ SamplerCardViewModel? inferenceDefaults = null,
IProgress? progress = null,
Func? onImportComplete = null,
Func? onImportCanceled = null,
@@ -116,15 +133,34 @@ public async Task DoImport(
return;
}
+ if (fileNameOverride != null && (fileNameOverride.Contains("\\") || fileNameOverride.Contains("/")))
+ {
+ // figure out the folder path to add to downloadFolder
+ var lastIndex = fileNameOverride.LastIndexOfAny(['\\', '/']);
+ if (lastIndex >= 0)
+ {
+ // Extract folder path and file name
+ var folderPath = fileNameOverride.Substring(0, lastIndex);
+ fileNameOverride = fileNameOverride.Substring(lastIndex + 1);
+
+ // Join with download folder
+ downloadFolder = downloadFolder.JoinDir(folderPath);
+ }
+ }
+
// Folders might be missing if user didn't install any packages yet
downloadFolder.Create();
+ var originalFileName =
+ fileNameOverride == null
+ ? modelFile.Name
+ : $@"{fileNameOverride}{Path.GetExtension(modelFile.Name)}";
+
// Fix invalid chars in FileName
- modelFile.Name = Path.GetInvalidFileNameChars()
- .Aggregate(modelFile.Name, (current, c) => current.Replace(c, '_'));
+ originalFileName = Path.GetInvalidFileNameChars()
+ .Aggregate(originalFileName, (current, c) => current.Replace(c, '_'));
- // New code: Ensure unique file name
- var originalFileName = modelFile.Name;
+ // Generate unique file name if it already exists
var uniqueFileName = GenerateUniqueFileName(downloadFolder.ToString(), originalFileName);
if (!uniqueFileName.Equals(originalFileName, StringComparison.Ordinal))
{
@@ -137,13 +173,19 @@ public async Task DoImport(
)
);
});
- modelFile.Name = uniqueFileName;
}
- var downloadPath = downloadFolder.JoinFile(modelFile.Name);
+ var downloadPath = downloadFolder.JoinFile(uniqueFileName);
// Download model info and preview first
- var cmInfoPath = await SaveCmInfo(model, modelVersion, modelFile, downloadFolder);
+ var cmInfoPath = await SaveCmInfo(
+ model,
+ modelVersion,
+ modelFile,
+ downloadFolder,
+ Path.GetFileNameWithoutExtension(uniqueFileName),
+ inferenceDefaults
+ );
var previewImagePath = await SavePreviewImage(modelVersion, downloadPath);
// Create tracked download
diff --git a/StabilityMatrix.Avalonia/Services/NavigationService.cs b/StabilityMatrix.Avalonia/Services/NavigationService.cs
index ad3b46105..fdeea33ac 100644
--- a/StabilityMatrix.Avalonia/Services/NavigationService.cs
+++ b/StabilityMatrix.Avalonia/Services/NavigationService.cs
@@ -60,7 +60,7 @@ public void NavigateTo(NavigationTransitionInfo? transitionInfo = nu
new FrameNavigationOptions
{
IsNavigationStackEnabled = true,
- TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo()
+ TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo(),
}
);
@@ -107,7 +107,7 @@ public void NavigateTo(
new FrameNavigationOptions
{
IsNavigationStackEnabled = true,
- TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo()
+ TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo(),
}
);
@@ -143,7 +143,7 @@ public void NavigateTo(ViewModelBase viewModel, NavigationTransitionInfo? transi
new FrameNavigationOptions
{
IsNavigationStackEnabled = true,
- TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo()
+ TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo(),
}
);
@@ -172,11 +172,11 @@ public bool GoBack()
new TypedNavigationEventArgs
{
ViewModelType = _frame.BackStack.Last().SourcePageType,
- ViewModel = _frame.BackStack.Last().Context
+ ViewModel = _frame.BackStack.Last().Context,
}
);
- _frame.GoBack();
+ _frame.GoBack(BetterSlideNavigationTransition.PageSlideFromLeft);
return true;
}
diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs
index 00b736dab..6aba7ec7a 100644
--- a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs
+++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs
@@ -82,9 +82,8 @@ IPyRunner pyRunner
{
var vulns = basePackage
.KnownVulnerabilities.Where(v => v.Severity == VulnerabilitySeverity.Critical)
- .Select(
- v =>
- $"**{v.Id}**: {v.Title}\n - Severity: {v.Severity}\n - Description: {v.Description}"
+ .Select(v =>
+ $"**{v.Id}**: {v.Title}\n - Severity: {v.Severity}\n - Description: {v.Description}"
)
.ToList();
@@ -133,9 +132,8 @@ IPyRunner pyRunner
else if (basePackage.HasVulnerabilities)
{
var vulns = basePackage
- .KnownVulnerabilities.Select(
- v =>
- $"**{v.Id}**: {v.Title}\n - Severity: {v.Severity}\n - Description: {v.Description}"
+ .KnownVulnerabilities.Select(v =>
+ $"**{v.Id}**: {v.Title}\n - Severity: {v.Severity}\n - Description: {v.Description}"
)
.ToList();
@@ -239,12 +237,13 @@ await basePackage.UpdateModelFolders(
.ToArray();
var launchProcessArgs = ProcessArgs.FromQuoted(launchArgStrings);
+ var runPackageOptions = new RunPackageOptions { Command = command, Arguments = launchProcessArgs };
// Join with extras, if any
await basePackage.RunPackage(
packagePath,
installedPackage,
- new RunPackageOptions { Command = command, Arguments = launchProcessArgs },
+ runPackageOptions,
console.Post,
cancellationToken
);
@@ -256,6 +255,7 @@ await basePackage.RunPackage(
notificationService,
this,
runningPackage,
+ runPackageOptions,
console
);
RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel);
diff --git a/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs b/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs
index dd29bba43..d681e4ff2 100644
--- a/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs
+++ b/StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs
@@ -1,4 +1,6 @@
-namespace StabilityMatrix.Avalonia.Services;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace StabilityMatrix.Avalonia.Services;
internal class ScopedServiceManager : IServiceManager
{
@@ -78,6 +80,13 @@ public T Get(Type serviceType)
// 3. If not scoped, delegate to the parent manager to resolve Singleton or Transient
// (Parent's Get will throw if the type isn't registered there either)
- return parentManager.Get(serviceType);
+ // return parentManager.Get(serviceType);
+
+ // We don't use parent manager for scoped contexts anymore,
+ // since we'll lose the scope through transients,
+ // then we have to make Inference Cards scoped as well,
+ // which cases samplers to be shared with Civit page and other issues.
+ // 3. Just use the scoped service provider, since we might need to keep the scope through transients as well
+ return (T)scopedServiceProvider.GetRequiredService(serviceType);
}
}
diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
index f42073451..dda31a288 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
@@ -263,6 +263,10 @@
InferenceWanTextToVideoView.axaml
Code
+
+ NrsCard.axaml
+ Code
+
diff --git a/StabilityMatrix.Avalonia/Styles/Card.axaml b/StabilityMatrix.Avalonia/Styles/Card.axaml
index d087cff72..1632eaf17 100644
--- a/StabilityMatrix.Avalonia/Styles/Card.axaml
+++ b/StabilityMatrix.Avalonia/Styles/Card.axaml
@@ -1,42 +1,43 @@
-
+
+ Width="330"
+ MaxHeight="450"
+ Margin="8">
+ Margin="8"
+ Classes="success">
+ Margin="8"
+ Classes="info">
+ Margin="8"
+ Classes="disabled">
+ Margin="8"
+ IsCardVisualsEnabled="False">
@@ -44,7 +45,7 @@
-
+
@@ -54,7 +55,7 @@
-
+
@@ -74,17 +75,17 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
index 2bb1f6788..ad48dea8d 100644
--- a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
+++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
@@ -2,20 +2,21 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
+ xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:fluentIcons="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent"
+ xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
xmlns:mocks="using:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
- xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
- xmlns:vendorLabs="clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs"
- xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters">
+ xmlns:vendorLabs="clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs">
-
+
-
+
-
+
+ Stretch="UniformToFill" />
+ Stretch="UniformToFill" />
+
@@ -221,27 +233,70 @@
Grid.RowSpan="2"
Width="36"
Height="36"
- CornerRadius="60"
+ CornerRadius="36"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding Local.PreviewImageFullPathGlobal, Converter={StaticResource FileUriConverter}}"
- Stretch="UniformToFill">
-
+ Stretch="UniformToFill" />
+
+
-
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml
index c05c4ca99..4b5753d30 100644
--- a/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml
+++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml
@@ -89,7 +89,7 @@
24
12
3
- 9999
+ 16
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/CivitDetailsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/CivitDetailsPage.axaml.cs
new file mode 100644
index 000000000..a02efb3b0
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/CivitDetailsPage.axaml.cs
@@ -0,0 +1,32 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Markup.Xaml;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Controls;
+
+namespace StabilityMatrix.Avalonia.Views;
+
+[RegisterTransient]
+public partial class CivitDetailsPage : UserControlBase
+{
+ public CivitDetailsPage()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void InputElement_OnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
+ {
+ if (sender is not ScrollViewer sv)
+ return;
+
+ var scrollAmount = e.Delta.Y * 75;
+ sv.Offset = new Vector(sv.Offset.X - scrollAmount, sv.Offset.Y);
+ e.Handled = true;
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml
new file mode 100644
index 000000000..feea147e4
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml.cs
new file mode 100644
index 000000000..cb3f9a577
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml.cs
@@ -0,0 +1,19 @@
+using Avalonia.Markup.Xaml;
+using Injectio.Attributes;
+using StabilityMatrix.Avalonia.Controls;
+
+namespace StabilityMatrix.Avalonia.Views.Dialogs;
+
+[RegisterTransient]
+public partial class ConfirmBulkDownloadDialog : UserControlBase
+{
+ public ConfirmBulkDownloadDialog()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmPackageDeleteDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmPackageDeleteDialog.axaml
index b9690b8df..955783f11 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmPackageDeleteDialog.axaml
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/ConfirmPackageDeleteDialog.axaml
@@ -1,55 +1,135 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
index d89f08868..e6d5b7654 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml
@@ -1,25 +1,31 @@
@@ -38,11 +44,11 @@
-->
-
+
@@ -51,32 +57,28 @@
VerticalAlignment="Stretch"
RowDefinitions="*,Auto">
-
-
+
+
+
-
-
+
+
-
+
-
+
@@ -93,18 +95,19 @@
Command="{StaticResource CopyImageAsBitmapCommand}"
CommandParameter="{Binding}"
HotKey="Shift+Ctrl+C"
- IsVisible="{OnPlatform Windows=True, Default=False}"
+ IsVisible="{OnPlatform Windows=True,
+ Default=False}"
Text="{x:Static lang:Resources.Action_CopyAsBitmap}" />
-
+
-
+ Text="Unsupported Format" />
@@ -124,8 +127,14 @@
VerticalAlignment="Top"
icons:Attached.Icon="fa-solid fa-info"
Classes="transparent-full"
- IsEnabled="{Binding HasGenerationParameters}"
- Tapped="InfoButton_OnTapped" />
+ Tapped="InfoButton_OnTapped">
+
+
+
+
+
+
+
@@ -133,60 +142,228 @@
Name="InfoTeachingTip"
Grid.Row="0"
MinWidth="100"
- PreferredPlacement="LeftBottom"
PlacementMargin="16,0,16,0"
+ PreferredPlacement="LeftBottom"
TailVisibility="Collapsed"
Target="{Binding #InfoButton}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+ Margin="0,-4,0,0"
+ Text="{Binding Metadata.Prompt}"
+ TextWrapping="Wrap"
+ ToolTip.ShowDelay="1000"
+ ToolTip.Tip="Click to Copy">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml.cs
index 4cd954906..76786b9bc 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml.cs
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml.cs
@@ -1,4 +1,6 @@
using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Mixins;
using Avalonia.Input;
using Injectio.Attributes;
using StabilityMatrix.Avalonia.Controls;
@@ -25,6 +27,7 @@ public bool IsFooterEnabled
public ImageViewerDialog()
{
InitializeComponent();
+ PressedMixin.Attach
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml
index ad58b3406..9cb70438c 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml
@@ -9,12 +9,8 @@
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
- xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
- xmlns:python="clr-namespace:StabilityMatrix.Core.Python;assembly=StabilityMatrix.Core"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
- xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="using:FluentAvalonia.UI.Controls"
- xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:vmDialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs"
d:DataContext="{x:Static mocks:DesignData.PythonPackagesViewModel}"
d:DesignHeight="450"
diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml
index 7bc8092e6..d0dba705c 100644
--- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml
+++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml
@@ -179,7 +179,7 @@
Margin="8,8,8,0"
VerticalAlignment="Top"
Background="#AA000000"
- ItemsSource="{Binding SelectedVersionViewModel.CivitFileViewModels}"
+ ItemsSource="{Binding SelectedVersionViewModel}"
SelectedItem="{Binding SelectedFile}">
diff --git a/StabilityMatrix.Avalonia/Views/HuggingFacePage.axaml b/StabilityMatrix.Avalonia/Views/HuggingFacePage.axaml
index a840d7f11..3bf4b010e 100644
--- a/StabilityMatrix.Avalonia/Views/HuggingFacePage.axaml
+++ b/StabilityMatrix.Avalonia/Views/HuggingFacePage.axaml
@@ -2,15 +2,16 @@
x:Class="StabilityMatrix.Avalonia.Views.HuggingFacePage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
+ xmlns:checkpointBrowser="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
+ xmlns:huggingFacePage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.HuggingFacePage"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:ui="using:FluentAvalonia.UI.Controls"
- xmlns:huggingFacePage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.HuggingFacePage"
- xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
- xmlns:checkpointBrowser="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser"
d:DataContext="{x:Static mocks:DesignData.HuggingFacePageViewModel}"
d:DesignHeight="650"
d:DesignWidth="800"
@@ -18,180 +19,194 @@
x:DataType="checkpointBrowser:HuggingFacePageViewModel"
Focusable="True"
mc:Ignorable="d">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
index 4e9877df3..1b3c4d5fa 100644
--- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
+++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
@@ -172,7 +172,7 @@ private void StartupInitialize(
Observable
.FromEventPattern(this, nameof(SizeChanged))
- .Where(x => x.EventArgs.PreviousSize != x.EventArgs.NewSize)
+ .Where(x => x.EventArgs.NewSize != x.EventArgs.PreviousSize)
.Throttle(TimeSpan.FromMilliseconds(100))
.Select(x => x.EventArgs.NewSize)
.ObserveOn(SynchronizationContext.Current!)
@@ -190,9 +190,13 @@ private void StartupInitialize(
}
else
{
+ // idk where these 30 pixels come from, probably title bar height? seems to be windows specific
+ var newHeight = Compat.IsWindows
+ ? Math.Max(0, newSize.Height - 30)
+ : newSize.Height;
s.WindowSettings = new WindowSettings(
newSize.Width,
- newSize.Height,
+ newHeight,
validWindowPosition ? Position.X : 0,
validWindowPosition ? Position.Y : 0,
WindowState == WindowState.Maximized
@@ -224,8 +228,8 @@ private void StartupInitialize(
else
{
s.WindowSettings = new WindowSettings(
- Width,
- Height,
+ s.WindowSettings?.Width ?? Width,
+ s.WindowSettings?.Height ?? Height,
validWindowPosition ? position.X : 0,
validWindowPosition ? position.Y : 0,
WindowState == WindowState.Maximized
diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml
index 95550aa04..eff51c8a6 100644
--- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml
@@ -10,6 +10,7 @@
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
+ xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:outputsPage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.OutputsPage"
xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll"
xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard"
@@ -48,7 +49,16 @@
Margin="0,0,12,0"
IsVisible="{Binding ShowFolders}"
ItemsSource="{Binding Categories}"
- SelectedItem="{Binding SelectedCategory}">
+ SelectedItem="{Binding SelectedCategory}"
+ SelectionMode="AlwaysSelected">
+
+
+
+
+
+
+
+
+ ToolTip.Tip="{Binding PackageDisplayName}" />
+
@@ -420,6 +437,7 @@
+
@@ -430,6 +448,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
@@ -483,6 +494,10 @@
+
+
-
+
-
-
+
+
+ Source="{Binding PreviewImageUri}"
+ Stretch="UniformToFill" />
-
-
-
+
+
+
-
-
-
+
-
+
+
+
+ Padding="4"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Top"
+ FontWeight="Light"
+ Tag="{Binding}">
+ TextAlignment="Center" />
+ Text="macOS"
+ TextAlignment="Center" />
+ TextAlignment="Center" />
@@ -229,14 +212,15 @@
-
+
-
+
@@ -245,39 +229,44 @@
-
+
-
-
+
+
-
+
-
+
@@ -293,36 +282,41 @@
-
-
+
+
-
+
-
+
@@ -335,6 +329,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml b/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml
index 2d738830d..ece303e2a 100644
--- a/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml
+++ b/StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml
@@ -3,6 +3,7 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonia="clr-namespace:FluentIcons.Avalonia;assembly=FluentIcons.Avalonia"
+ xmlns:avalonia1="https://github.com/projektanker/icons.avalonia"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:database="clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core"
@@ -12,6 +13,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:packageManager="clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager"
+ xmlns:python="clr-namespace:StabilityMatrix.Core.Python;assembly=StabilityMatrix.Core"
d:DataContext="{x:Static designData:DesignData.PackageInstallDetailViewModel}"
d:DesignHeight="850"
d:DesignWidth="800"
@@ -59,7 +61,8 @@
Grid.Column="0"
Content="Branches"
CornerRadius="8,0,0,8"
- IsChecked="{Binding !IsReleaseMode, Mode=TwoWay}" />
+ IsChecked="{Binding !IsReleaseMode, Mode=TwoWay}"
+ IsEnabled="{Binding ShowBranchMode}" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml
index 3cdda185a..6b7b201f8 100644
--- a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml
@@ -168,7 +168,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml
index 3d4bab0a3..433954428 100644
--- a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml
@@ -9,11 +9,11 @@
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
+ xmlns:models1="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:native="clr-namespace:StabilityMatrix.Native;assembly=StabilityMatrix.Native"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="using:FluentAvalonia.UI.Controls"
- xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings"
d:DataContext="{x:Static mocks:DesignData.InferenceSettingsViewModel}"
d:DesignHeight="650"
@@ -27,6 +27,10 @@
+
+
+
+
@@ -165,6 +169,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
index 4ed3d7089..18d191a4f 100644
--- a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
@@ -3,6 +3,7 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
+ xmlns:checkpointManager="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -12,7 +13,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
+ xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
+ xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings"
d:DataContext="{x:Static mocks:DesignData.MainSettingsViewModel}"
@@ -154,7 +157,7 @@
-
+
+
+
@@ -220,10 +225,23 @@
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Core/Api/ICivitApi.cs b/StabilityMatrix.Core/Api/ICivitApi.cs
index b0dd41338..55397d58f 100644
--- a/StabilityMatrix.Core/Api/ICivitApi.cs
+++ b/StabilityMatrix.Core/Api/ICivitApi.cs
@@ -4,6 +4,7 @@
namespace StabilityMatrix.Core.Api;
+[Headers("User-Agent: StabilityMatrix/1.0")]
public interface ICivitApi
{
[Get("/api/v1/models")]
diff --git a/StabilityMatrix.Core/Api/ICivitTRPCApi.cs b/StabilityMatrix.Core/Api/ICivitTRPCApi.cs
index 28f39377f..8d51f6f1a 100644
--- a/StabilityMatrix.Core/Api/ICivitTRPCApi.cs
+++ b/StabilityMatrix.Core/Api/ICivitTRPCApi.cs
@@ -52,4 +52,11 @@ Task ToggleFavoriteModel(
[Authorize] string bearerToken,
CancellationToken cancellationToken = default
);
+
+ [QueryUriFormat(UriFormat.UriEscaped)]
+ [Get("/api/trpc/image.getGenerationData")]
+ Task> GetImageGenerationData(
+ [Query] string input,
+ CancellationToken cancellationToken = default
+ );
}
diff --git a/StabilityMatrix.Core/Api/IHuggingFaceApi.cs b/StabilityMatrix.Core/Api/IHuggingFaceApi.cs
new file mode 100644
index 000000000..ecd552147
--- /dev/null
+++ b/StabilityMatrix.Core/Api/IHuggingFaceApi.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+using Refit;
+using StabilityMatrix.Core.Models.Api.HuggingFace;
+
+namespace StabilityMatrix.Core.Api;
+
+public interface IHuggingFaceApi
+{
+ [Get("/api/whoami-v2")]
+ Task> GetCurrentUserAsync([Header("Authorization")] string authorization);
+}
diff --git a/StabilityMatrix.Core/Database/LiteDbContext.cs b/StabilityMatrix.Core/Database/LiteDbContext.cs
index 7f1ee8ec0..cdd2156b7 100644
--- a/StabilityMatrix.Core/Database/LiteDbContext.cs
+++ b/StabilityMatrix.Core/Database/LiteDbContext.cs
@@ -1,6 +1,9 @@
using System.Collections.Immutable;
+using System.Globalization;
+using AsyncAwaitBestPractices;
using LiteDB;
using LiteDB.Async;
+using LiteDB.Engine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StabilityMatrix.Core.Extensions;
@@ -73,8 +76,24 @@ private LiteDatabaseAsync CreateDatabase()
{
var dbPath = Path.Combine(settingsManager.LibraryDir, "StabilityMatrix.db");
db = new LiteDatabaseAsync(
- new ConnectionString() { Filename = dbPath, Connection = ConnectionType.Shared, }
+ new ConnectionString { Filename = dbPath, Connection = ConnectionType.Shared }
);
+
+ var sortOption = db.Collation.SortOptions;
+ if (sortOption is not CompareOptions.Ordinal)
+ {
+ logger.LogDebug(
+ "Database collation is not Ordinal ({SortOption}), rebuilding...",
+ sortOption
+ );
+
+ var options = new RebuildOptions
+ {
+ Collation = new Collation(CultureInfo.InvariantCulture.LCID, CompareOptions.Ordinal),
+ };
+
+ db.RebuildAsync(options).GetAwaiter().GetResult();
+ }
}
catch (IOException e)
{
@@ -100,11 +119,10 @@ private LiteDatabaseAsync CreateDatabase()
{
var version = await CivitModelVersions
.Query()
- .Where(
- mv =>
- mv.Files!.Select(f => f.Hashes)
- .Select(hashes => hashes.BLAKE3)
- .Any(hash => hash == hashBlake3)
+ .Where(mv =>
+ mv.Files!.Select(f => f.Hashes)
+ .Select(hashes => hashes.BLAKE3)
+ .Any(hash => hash == hashBlake3)
)
.FirstOrDefaultAsync()
.ConfigureAwait(false);
@@ -201,7 +219,7 @@ public async Task ClearAllCacheCollectionsAsync()
nameof(CivitModelQueryCache),
nameof(GithubCache),
nameof(LocalModelFiles),
- nameof(LocalImageFiles)
+ nameof(LocalImageFiles),
};
logger.LogInformation("Clearing all cache collections: [{@Names}]", collectionNames);
diff --git a/StabilityMatrix.Core/Exceptions/CivitLoginRequiredException.cs b/StabilityMatrix.Core/Exceptions/CivitLoginRequiredException.cs
new file mode 100644
index 000000000..c114e8033
--- /dev/null
+++ b/StabilityMatrix.Core/Exceptions/CivitLoginRequiredException.cs
@@ -0,0 +1,3 @@
+namespace StabilityMatrix.Core.Exceptions;
+
+public class CivitLoginRequiredException : UnauthorizedAccessException;
diff --git a/StabilityMatrix.Core/Exceptions/HuggingFaceLoginRequiredException.cs b/StabilityMatrix.Core/Exceptions/HuggingFaceLoginRequiredException.cs
new file mode 100644
index 000000000..29a497c21
--- /dev/null
+++ b/StabilityMatrix.Core/Exceptions/HuggingFaceLoginRequiredException.cs
@@ -0,0 +1,3 @@
+namespace StabilityMatrix.Core.Exceptions;
+
+public class HuggingFaceLoginRequiredException : UnauthorizedAccessException;
diff --git a/StabilityMatrix.Core/Git/CommandGitVersionProvider.cs b/StabilityMatrix.Core/Git/CommandGitVersionProvider.cs
index cedf0cfc1..c9c45ba13 100644
--- a/StabilityMatrix.Core/Git/CommandGitVersionProvider.cs
+++ b/StabilityMatrix.Core/Git/CommandGitVersionProvider.cs
@@ -26,7 +26,7 @@ public async Task> FetchTagsAsync(
"ls-remote",
"--tags",
"--sort=-v:refname",
- repositoryUri
+ repositoryUri,
];
var result = await prerequisiteHelper
@@ -36,10 +36,16 @@ public async Task> FetchTagsAsync(
if (result is { IsSuccessExitCode: true, StandardOutput: not null })
{
- var tagLines = result.StandardOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
- var tagNames = tagLines
+ var tagNames = result
+ .StandardOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Split('\t').LastOrDefault()?.Replace("refs/tags/", "").Trim())
- .Where(line => !string.IsNullOrWhiteSpace(line))
+ .Where(s => !string.IsNullOrWhiteSpace(s))
+ .Select(s =>
+ {
+ const string peel = "^{}";
+ return s!.EndsWith(peel, StringComparison.Ordinal) ? s[..^peel.Length] : s;
+ })
+ .Distinct()
.Take(limit > 0 ? limit : int.MaxValue);
tags.AddRange(tagNames.Select(tag => new GitVersion { Tag = tag }));
diff --git a/StabilityMatrix.Core/Helper/EventManager.cs b/StabilityMatrix.Core/Helper/EventManager.cs
index dcde564d5..41d5e8859 100644
--- a/StabilityMatrix.Core/Helper/EventManager.cs
+++ b/StabilityMatrix.Core/Helper/EventManager.cs
@@ -4,6 +4,7 @@
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Inference;
using StabilityMatrix.Core.Models.PackageModification;
+using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Models.Update;
@@ -24,7 +25,13 @@ private EventManager() { }
public event EventHandler? DevModeSettingChanged;
public event EventHandler? UpdateAvailable;
public event EventHandler? PackageLaunchRequested;
- public event EventHandler? PackageRelaunchRequested;
+
+ public delegate Task PackageRelaunchRequestedEventHandler(
+ object? sender,
+ InstalledPackage package,
+ RunPackageOptions runPackageOptions
+ );
+ public event PackageRelaunchRequestedEventHandler? PackageRelaunchRequested;
public event EventHandler? ScrollToBottomRequested;
public event EventHandler? ProgressChanged;
public event EventHandler? RunningPackageStatusChanged;
@@ -101,8 +108,8 @@ public void OnDownloadsTeachingTipRequested() =>
public void OnRecommendedModelsDialogClosed() =>
RecommendedModelsDialogClosed?.Invoke(this, EventArgs.Empty);
- public void OnPackageRelaunchRequested(InstalledPackage package) =>
- PackageRelaunchRequested?.Invoke(this, package);
+ public void OnPackageRelaunchRequested(InstalledPackage package, RunPackageOptions runPackageOptions) =>
+ PackageRelaunchRequested?.Invoke(this, package, runPackageOptions);
public void OnWorkflowInstalled() => WorkflowInstalled?.Invoke(this, EventArgs.Empty);
diff --git a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
index 7630b55da..09bda13f9 100644
--- a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
+++ b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs
@@ -15,6 +15,8 @@ public class PackageFactory : IPackageFactory
private readonly IDownloadService downloadService;
private readonly IPrerequisiteHelper prerequisiteHelper;
private readonly IPyRunner pyRunner;
+ private readonly IUvManager uvManager;
+ private readonly IPyInstallationManager pyInstallationManager;
///
/// Mapping of package.Name to package
@@ -27,6 +29,7 @@ public PackageFactory(
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager,
IPyRunner pyRunner
)
{
@@ -35,6 +38,7 @@ IPyRunner pyRunner
this.downloadService = downloadService;
this.prerequisiteHelper = prerequisiteHelper;
this.pyRunner = pyRunner;
+ this.pyInstallationManager = pyInstallationManager;
this.basePackages = basePackages.ToDictionary(x => x.Name);
}
@@ -42,65 +46,197 @@ 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),
- "sdfx" => new Sdfx(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "mashb1t-fooocus"
- => new Mashb1tFooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "reforge" => new Reforge(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "FluxGym" => new FluxGym(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "SimpleSDXL"
- => new SimpleSDXL(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "Cogstudio"
- => new Cogstudio(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "ComfyUI-Zluda"
- => new ComfyZluda(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "stable-diffusion-webui-amdgpu-forge"
- => new ForgeAmdGpu(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- "forge-classic"
- => new ForgeClassic(githubApiCache, settingsManager, downloadService, prerequisiteHelper),
- _ => throw new ArgumentOutOfRangeException(nameof(installedPackage))
+ "ComfyUI" => new ComfyUI(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "Fooocus" => new Fooocus(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "stable-diffusion-webui" => new A3WebUI(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "Fooocus-ControlNet-SDXL" => new FocusControlNet(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "Fooocus-MRE" => new FooocusMre(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "InvokeAI" => new InvokeAI(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "kohya_ss" => new KohyaSs(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyRunner,
+ pyInstallationManager
+ ),
+ "OneTrainer" => new OneTrainer(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "RuinedFooocus" => new RuinedFooocus(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "stable-diffusion-webui-forge" => new SDWebForge(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "stable-diffusion-webui-directml" => new StableDiffusionDirectMl(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "stable-diffusion-webui-ux" => new StableDiffusionUx(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "StableSwarmUI" => new StableSwarm(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "automatic" => new VladAutomatic(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "voltaML-fast-stable-diffusion" => new VoltaML(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "sdfx" => new Sdfx(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "mashb1t-fooocus" => new Mashb1tFooocus(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "reforge" => new Reforge(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "FluxGym" => new FluxGym(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "SimpleSDXL" => new SimpleSDXL(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "Cogstudio" => new Cogstudio(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "ComfyUI-Zluda" => new ComfyZluda(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "stable-diffusion-webui-amdgpu-forge" => new ForgeAmdGpu(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "forge-classic" => new ForgeClassic(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "framepack" => new FramePack(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "framepack-studio" => new FramePackStudio(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ "ai-toolkit" => new AiToolkit(
+ githubApiCache,
+ settingsManager,
+ downloadService,
+ prerequisiteHelper,
+ pyInstallationManager
+ ),
+ _ => throw new ArgumentOutOfRangeException(nameof(installedPackage)),
};
}
diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs
index 49b66e267..f9ab746a4 100644
--- a/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs
+++ b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs
@@ -60,9 +60,61 @@ public bool IsLegacyNvidiaGpu()
return ComputeCapabilityValue < 7.5m;
}
+ public bool IsWindowsRocmSupportedGpu()
+ {
+ var gfx = GetAmdGfxArch();
+ if (gfx is null)
+ return false;
+
+ return gfx.StartsWith("gfx110") || gfx.StartsWith("gfx120") || gfx.Equals("gfx1151");
+ }
+
public bool IsAmd => Name?.Contains("amd", StringComparison.OrdinalIgnoreCase) ?? false;
public bool IsIntel => Name?.Contains("arc", StringComparison.OrdinalIgnoreCase) ?? false;
+ public string? GetAmdGfxArch()
+ {
+ if (!IsAmd || string.IsNullOrWhiteSpace(Name))
+ return null;
+
+ var name = Name.ToLowerInvariant();
+
+ if (name.Contains("9070") || name.Contains("R9700"))
+ return "gfx1201";
+
+ if (name.Contains("9060"))
+ return "gfx1200";
+
+ if (name.Contains("z2") || name.Contains("880m") || name.Contains("8050s") || name.Contains("8060s"))
+ return "gfx1151";
+
+ if (name.Contains("740m") || name.Contains("760m") || name.Contains("780m") || name.Contains("z1"))
+ return "gfx1103";
+
+ if (
+ name.Contains("w7400")
+ || name.Contains("w7500")
+ || name.Contains("w7600")
+ || name.Contains("7500 xt")
+ || name.Contains("7600")
+ || name.Contains("7650 gre")
+ || name.Contains("7700s")
+ )
+ return "gfx1102";
+
+ if (
+ name.Contains("v710")
+ || name.Contains("7700")
+ || (name.Contains("7800") && !name.Contains("w7800"))
+ )
+ return "gfx1101";
+
+ if (name.Contains("w7800") || name.Contains("7900") || name.Contains("7950") || name.Contains("7990"))
+ return "gfx1100";
+
+ return null;
+ }
+
public virtual bool Equals(GpuInfo? other)
{
if (other is null)
diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs b/StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs
index 4c58618f3..621049796 100644
--- a/StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs
+++ b/StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs
@@ -70,7 +70,7 @@ private static IEnumerable IterGpuInfoWindows()
[SupportedOSPlatform("linux")]
private static IEnumerable IterGpuInfoLinux()
{
- var output = RunBashCommand("lspci | grep -E \"(VGA|3D)\"");
+ var output = RunBashCommand("lspci | grep -E '(VGA|3D)'");
var gpuLines = output.Split("\n");
var gpuIndex = 0;
@@ -87,10 +87,10 @@ private static IEnumerable IterGpuInfoLinux()
string? name = null;
// Parse output with regex
- var match = Regex.Match(gpuOutput, @"VGA compatible controller: ([^\n]*)");
+ var match = Regex.Match(gpuOutput, @"(VGA compatible controller|3D controller): ([^\n]*)");
if (match.Success)
{
- name = match.Groups[1].Value.Trim();
+ name = match.Groups[2].Value.Trim();
}
match = Regex.Match(gpuOutput, @"prefetchable\) \[size=(\\d+)M\]");
@@ -267,6 +267,12 @@ public static bool HasLegacyNvidiaGpu()
.Any(gpu => gpu is { IsNvidia: true, Name: not null, ComputeCapabilityValue: < 7.5m });
}
+ public static bool HasAmpereOrNewerGpu()
+ {
+ return IterGpuInfo()
+ .Any(gpu => gpu is { IsNvidia: true, Name: not null, ComputeCapabilityValue: >= 8.6m });
+ }
+
///
/// Return true if the system has at least one AMD GPU.
///
@@ -275,13 +281,22 @@ public static bool HasAmdGpu()
return IterGpuInfo().Any(gpu => gpu.IsAmd);
}
+ public static bool HasWindowsRocmSupportedGpu() =>
+ IterGpuInfo().Any(gpu => gpu is { IsAmd: true, Name: not null } && gpu.IsWindowsRocmSupportedGpu());
+
+ public static GpuInfo? GetWindowsRocmSupportedGpu()
+ {
+ return IterGpuInfo().FirstOrDefault(gpu => gpu.IsWindowsRocmSupportedGpu());
+ }
+
public static bool HasIntelGpu() => IterGpuInfo().Any(gpu => gpu.IsIntel);
// Set ROCm for default if AMD and Linux
public static bool PreferRocm() => !HasNvidiaGpu() && HasAmdGpu() && Compat.IsLinux;
// Set DirectML for default if AMD and Windows
- public static bool PreferDirectMLOrZluda() => !HasNvidiaGpu() && HasAmdGpu() && Compat.IsWindows;
+ public static bool PreferDirectMLOrZluda() =>
+ !HasNvidiaGpu() && HasAmdGpu() && Compat.IsWindows && !HasWindowsRocmSupportedGpu();
private static readonly Lazy IsMemoryInfoAvailableLazy = new(() => TryGetMemoryInfo(out _));
public static bool IsMemoryInfoAvailable => IsMemoryInfoAvailableLazy.Value;
diff --git a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs
index 3b5aa7311..a99a525a2 100644
--- a/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs
+++ b/StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs
@@ -1,10 +1,12 @@
using System.Diagnostics;
using System.Runtime.Versioning;
+using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
namespace StabilityMatrix.Core.Helper;
@@ -17,6 +19,9 @@ public interface IPrerequisiteHelper
bool IsHipSdkInstalled { get; }
Task InstallAllIfNecessary(IProgress? progress = null);
+ Task InstallUvIfNecessary(IProgress? progress = null);
+ string UvExePath { get; }
+ bool IsUvInstalled { get; }
Task UnpackResourcesIfNecessary(IProgress? progress = null);
Task InstallGitIfNecessary(IProgress? progress = null);
Task InstallPythonIfNecessary(IProgress? progress = null);
@@ -84,32 +89,41 @@ async Task CloneGitRepository(
Action? onProcessOutput = null
)
{
- // Latest if no version is given
- if (version is null)
+ // Decide shallow clone only when not pinning to arbitrary commit post-clone
+ var isShallowOk = version is null || version.Tag is not null;
+
+ var cloneArgs = new ProcessArgsBuilder("clone");
+ if (isShallowOk)
{
- await RunGit(["clone", "--depth", "1", repositoryUrl], onProcessOutput, rootDir)
- .ConfigureAwait(false);
+ cloneArgs = cloneArgs.AddArgs("--depth", "1", "--single-branch");
}
- else if (version.Tag is not null)
+
+ if (!string.IsNullOrWhiteSpace(version?.Tag))
{
- await RunGit(["clone", "--depth", "1", version.Tag, repositoryUrl], onProcessOutput, rootDir)
- .ConfigureAwait(false);
+ cloneArgs = cloneArgs.AddArgs("--branch", version.Tag!);
}
- else if (version.Branch is not null && version.CommitSha is not null)
+ else if (!string.IsNullOrWhiteSpace(version?.Branch))
+ {
+ cloneArgs = cloneArgs.AddArgs("--branch", version.Branch!);
+ }
+
+ cloneArgs = cloneArgs.AddArgs(repositoryUrl);
+
+ await RunGit(cloneArgs.ToProcessArgs(), onProcessOutput, rootDir).ConfigureAwait(false);
+
+ // If pinning to a specific commit, we need a destination directory to continue
+ if (!string.IsNullOrWhiteSpace(version?.CommitSha))
{
+ await RunGit(["fetch", "--depth", "1", "origin", version.CommitSha!], onProcessOutput, rootDir)
+ .ConfigureAwait(false);
+ await RunGit(["checkout", "--force", version.CommitSha!], onProcessOutput, rootDir)
+ .ConfigureAwait(false);
await RunGit(
- ["clone", "--depth", "1", "--branch", version.Branch, repositoryUrl],
+ ["submodule", "update", "--init", "--recursive", "--depth", "1"],
onProcessOutput,
rootDir
)
.ConfigureAwait(false);
-
- await RunGit(["checkout", version.CommitSha, "--force"], onProcessOutput, rootDir)
- .ConfigureAwait(false);
- }
- else
- {
- throw new ArgumentException("Version must have a tag or branch and commit sha.", nameof(version));
}
}
@@ -117,7 +131,10 @@ async Task UpdateGitRepository(
string repositoryDir,
string repositoryUrl,
GitVersion version,
- Action? onProcessOutput = null
+ Action? onProcessOutput = null,
+ bool usePrune = false,
+ bool allowRebaseFallback = true,
+ bool allowResetHardFallback = false
)
{
if (!Directory.Exists(Path.Combine(repositoryDir, ".git")))
@@ -127,6 +144,10 @@ await RunGit(["remote", "add", "origin", repositoryUrl], onProcessOutput, reposi
.ConfigureAwait(false);
}
+ // Ensure origin url matches the expected one
+ await RunGit(["remote", "set-url", "origin", repositoryUrl], onProcessOutput, repositoryDir)
+ .ConfigureAwait(false);
+
// Specify Tag
if (version.Tag is not null)
{
@@ -141,27 +162,79 @@ await RunGit(["submodule", "update", "--init", "--recursive"], onProcessOutput,
// Specify Branch + CommitSha
else if (version.Branch is not null && version.CommitSha is not null)
{
- await RunGit(["fetch", "--force"], onProcessOutput, repositoryDir).ConfigureAwait(false);
+ await RunGit(["fetch", "--force", "origin", version.CommitSha], onProcessOutput, repositoryDir)
+ .ConfigureAwait(false);
- await RunGit(["checkout", version.CommitSha, "--force"], onProcessOutput, repositoryDir)
+ await RunGit(["checkout", "--force", version.CommitSha], onProcessOutput, repositoryDir)
.ConfigureAwait(false);
// Update submodules
- await RunGit(["submodule", "update", "--init", "--recursive"], onProcessOutput, repositoryDir)
+ await RunGit(
+ ["submodule", "update", "--init", "--recursive", "--depth", "1"],
+ onProcessOutput,
+ repositoryDir
+ )
.ConfigureAwait(false);
}
// Specify Branch (Use latest commit)
else if (version.Branch is not null)
{
- // Fetch
- await RunGit(["fetch", "--force"], onProcessOutput, repositoryDir).ConfigureAwait(false);
+ // Fetch (optional prune)
+ var fetchArgs = new ProcessArgsBuilder("fetch", "--force");
+ if (usePrune)
+ fetchArgs = fetchArgs.AddArg("--prune");
+ fetchArgs = fetchArgs.AddArg("origin");
+ await RunGit(fetchArgs.ToProcessArgs(), onProcessOutput, repositoryDir).ConfigureAwait(false);
+
// Checkout
- await RunGit(["checkout", version.Branch, "--force"], onProcessOutput, repositoryDir)
+ await RunGit(["checkout", "--force", version.Branch], onProcessOutput, repositoryDir)
.ConfigureAwait(false);
- // Pull latest
- await RunGit(["pull", "--autostash", "origin", version.Branch], onProcessOutput, repositoryDir)
+
+ // Try ff-only first
+ var ffOnlyResult = await GetGitOutput(
+ ["pull", "--ff-only", "--autostash", "origin", version.Branch],
+ repositoryDir
+ )
.ConfigureAwait(false);
+
+ if (ffOnlyResult.ExitCode != 0)
+ {
+ if (allowRebaseFallback)
+ {
+ var rebaseResult = await GetGitOutput(
+ ["pull", "--rebase", "--autostash", "origin", version.Branch],
+ repositoryDir
+ )
+ .ConfigureAwait(false);
+
+ rebaseResult.EnsureSuccessExitCode();
+ }
+ else if (allowResetHardFallback)
+ {
+ await RunGit(
+ ["fetch", "--force", "origin", version.Branch],
+ onProcessOutput,
+ repositoryDir
+ )
+ .ConfigureAwait(false);
+ await RunGit(
+ ["reset", "--hard", $"origin/{version.Branch}"],
+ onProcessOutput,
+ repositoryDir
+ )
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ ffOnlyResult.EnsureSuccessExitCode();
+ }
+ }
+
// Update submodules
- await RunGit(["submodule", "update", "--init", "--recursive"], onProcessOutput, repositoryDir)
+ await RunGit(
+ ["submodule", "update", "--init", "--recursive", "--depth", "1"],
+ onProcessOutput,
+ repositoryDir
+ )
.ConfigureAwait(false);
}
// Not specified
@@ -186,10 +259,22 @@ Task RunNpm(
Action? onProcessOutput = null,
IReadOnlyDictionary? envVars = null
);
+
+ AnsiProcess RunNpmDetached(
+ ProcessArgs args,
+ string? workingDirectory = null,
+ Action? onProcessOutput = null,
+ IReadOnlyDictionary? envVars = null
+ );
Task InstallNodeIfNecessary(IProgress? progress = null);
- Task InstallPackageRequirements(BasePackage package, IProgress? progress = null);
+ Task InstallPackageRequirements(
+ BasePackage package,
+ PyVersion? pyVersion = null,
+ IProgress? progress = null
+ );
Task InstallPackageRequirements(
List prerequisites,
+ PyVersion? pyVersion = null,
IProgress? progress = null
);
@@ -204,5 +289,12 @@ Task RunDotnet(
);
Task FixGitLongPaths();
- Task AddMissingLibsToVenv(DirectoryPath installedPackagePath, IProgress? progress = null);
+ Task AddMissingLibsToVenv(
+ DirectoryPath installedPackagePath,
+ PyBaseInstall baseInstall,
+ IProgress? progress = null
+ );
+ Task InstallPythonIfNecessary(PyVersion version, IProgress? progress = null);
+ Task InstallVirtualenvIfNecessary(PyVersion version, IProgress? progress = null);
+ Task InstallTkinterIfNecessary(PyVersion version, IProgress? progress = null);
}
diff --git a/StabilityMatrix.Core/Helper/ImageMetadata.cs b/StabilityMatrix.Core/Helper/ImageMetadata.cs
index 797d82a30..8cdf05103 100644
--- a/StabilityMatrix.Core/Helper/ImageMetadata.cs
+++ b/StabilityMatrix.Core/Helper/ImageMetadata.cs
@@ -7,7 +7,6 @@
using MetadataExtractor.Formats.Exif;
using MetadataExtractor.Formats.Png;
using MetadataExtractor.Formats.WebP;
-using Microsoft.VisualBasic;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
@@ -91,6 +90,19 @@ public static (
return (null, paramsJson, smProj, null, null);
}
+ if (
+ filePath.Extension.Equals(".jpeg", StringComparison.OrdinalIgnoreCase)
+ || filePath.Extension.Equals(".jpg", StringComparison.OrdinalIgnoreCase)
+ )
+ {
+ var file = ImageFile.FromFile(filePath.Info.FullName);
+ var userComment = file.Properties.Get(ExifTag.UserComment);
+ var bytes = userComment.Interoperability.Data.Skip(8).ToArray();
+ var userCommentString = Encoding.BigEndianUnicode.GetString(bytes);
+
+ return (null, null, null, null, userCommentString);
+ }
+
using var stream = filePath.Info.OpenRead();
using var reader = new BinaryReader(stream);
diff --git a/StabilityMatrix.Core/Helper/ProcessTracker.cs b/StabilityMatrix.Core/Helper/ProcessTracker.cs
index 7bdd506b4..a1057915a 100644
--- a/StabilityMatrix.Core/Helper/ProcessTracker.cs
+++ b/StabilityMatrix.Core/Helper/ProcessTracker.cs
@@ -20,31 +20,30 @@ public static partial class ProcessTracker
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
- private static readonly Lazy ProcessTrackerJobLazy =
- new(() =>
+ private static readonly Lazy ProcessTrackerJobLazy = new(() =>
+ {
+ if (!JobObject.IsAvailableOnCurrentPlatform)
{
- if (!JobObject.IsAvailableOnCurrentPlatform)
- {
- return null;
- }
+ return null;
+ }
- // The job name is optional (and can be null) but it helps with diagnostics.
- // If it's not null, it has to be unique. Use SysInternals' Handle command-line
- // utility: handle -a ChildProcessTracker
- var jobName = $"SM_ProcessTracker_{Environment.ProcessId}";
+ // The job name is optional (and can be null) but it helps with diagnostics.
+ // If it's not null, it has to be unique. Use SysInternals' Handle command-line
+ // utility: handle -a ChildProcessTracker
+ var jobName = $"SM_ProcessTracker_{Environment.ProcessId}";
- Logger.Debug("Creating Job Object {Job}", jobName);
+ Logger.Debug("Creating Job Object {Job}", jobName);
- try
- {
- return new JobObject(jobName);
- }
- catch (Exception e)
- {
- Logger.Error(e, "Failed to create Job Object, ProcessTracker will be unavailable");
- return null;
- }
- });
+ try
+ {
+ return new JobObject(jobName);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Failed to create Job Object, ProcessTracker will be unavailable");
+ return null;
+ }
+ });
private static JobObject? ProcessTrackerJob => ProcessTrackerJobLazy.Value;
@@ -76,15 +75,15 @@ public static void AddProcess(Process process)
return;
}
- Logger.Debug(
- "Adding Process {Process} [{Id}] to Job Object {Job}",
- process.ProcessName,
- process.Id,
- job.Name
- );
-
try
{
+ Logger.Debug(
+ "Adding Process {Process} [{Id}] to Job Object {Job}",
+ process.ProcessName,
+ process.Id,
+ job.Name
+ );
+
job.AssignProcess(process);
}
catch (Exception)
@@ -200,14 +199,14 @@ public JobObject(string name)
Name = name;
- handle = CreateJobObjectA(IntPtr.Zero, name);
+ handle = CreateJobObject(IntPtr.Zero, name);
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
// This is the key flag. When our process is killed, Windows will automatically
// close the job handle, and when that happens, we want the child processes to
// be killed, too.
- LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
+ LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
};
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
@@ -272,10 +271,15 @@ protected override bool ReleaseHandle()
}
}
- [LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
- private static partial IntPtr CreateJobObjectA(IntPtr lpJobAttributes, string name);
+ [LibraryImport(
+ "kernel32.dll",
+ EntryPoint = "CreateJobObjectW",
+ SetLastError = true,
+ StringMarshalling = StringMarshalling.Utf16
+ )]
+ private static partial IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);
- [LibraryImport("kernel32.dll")]
+ [LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetInformationJobObject(
IntPtr job,
@@ -304,7 +308,7 @@ internal enum JobObjectInfoType
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
- GroupInformation = 11
+ GroupInformation = 11,
}
[StructLayout(LayoutKind.Sequential)]
@@ -326,7 +330,7 @@ internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
// ReSharper disable once IdentifierTypo
internal enum JOBOBJECTLIMIT : uint
{
- JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000,
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/StabilityMatrix.Core/Helper/SharedFolders.cs b/StabilityMatrix.Core/Helper/SharedFolders.cs
index 7a77b7238..cf73d2e6a 100644
--- a/StabilityMatrix.Core/Helper/SharedFolders.cs
+++ b/StabilityMatrix.Core/Helper/SharedFolders.cs
@@ -20,16 +20,15 @@ public class SharedFolders(ISettingsManager settingsManager, IPackageFactory pac
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
// mapping is old:new
- private static readonly Dictionary LegacySharedFolderMapping =
- new()
- {
- { "CLIP", "TextEncoders" },
- { "Unet", "DiffusionModels" },
- { "InvokeClipVision", "ClipVision" },
- { "InvokeIpAdapters15", "IpAdapters15" },
- { "InvokeIpAdaptersXl", "IpAdaptersXl" },
- { "TextualInversion", "Embeddings" }
- };
+ private static readonly Dictionary LegacySharedFolderMapping = new()
+ {
+ { "CLIP", "TextEncoders" },
+ { "Unet", "DiffusionModels" },
+ { "InvokeClipVision", "ClipVision" },
+ { "InvokeIpAdapters15", "IpAdapters15" },
+ { "InvokeIpAdaptersXl", "IpAdaptersXl" },
+ { "TextualInversion", "Embeddings" },
+ };
public bool IsDisposed { get; private set; }
@@ -72,6 +71,12 @@ public static async Task CreateOrUpdateLink(
sourceDir.Create();
}
+ var destAsFile = new FilePath(destinationDir.ToString());
+ if (destAsFile.Exists)
+ {
+ await destAsFile.DeleteAsync().ConfigureAwait(false);
+ }
+
if (destinationDir.Exists)
{
// Existing dest is a link
diff --git a/StabilityMatrix.Core/Helper/Utilities.cs b/StabilityMatrix.Core/Helper/Utilities.cs
index c515c2669..e98d55da8 100644
--- a/StabilityMatrix.Core/Helper/Utilities.cs
+++ b/StabilityMatrix.Core/Helper/Utilities.cs
@@ -106,6 +106,33 @@ public static string RemoveHtml(string? stringWithHtml)
return pruned;
}
+ ///
+ /// Returns the simplified aspect ratio as a tuple: (widthRatio, heightRatio).
+ /// e.g. GetAspectRatio(1920,1080) -> (16,9)
+ ///
+ public static (int widthRatio, int heightRatio) GetAspectRatio(int width, int height)
+ {
+ if (width <= 0 || height <= 0)
+ throw new ArgumentException("Width and height must be positive.");
+
+ var gcd = Gcd(width, height);
+ return (width / gcd, height / gcd);
+ }
+
+ // Euclidean GCD
+ private static int Gcd(int a, int b)
+ {
+ a = Math.Abs(a);
+ b = Math.Abs(b);
+ while (b != 0)
+ {
+ var rem = a % b;
+ a = b;
+ b = rem;
+ }
+ return a;
+ }
+
[GeneratedRegex("<[^>]+>")]
private static partial Regex HtmlRegex();
}
diff --git a/StabilityMatrix.Core/Models/Api/CivitFile.cs b/StabilityMatrix.Core/Models/Api/CivitFile.cs
index fa2c9d226..18ace08c0 100644
--- a/StabilityMatrix.Core/Models/Api/CivitFile.cs
+++ b/StabilityMatrix.Core/Models/Api/CivitFile.cs
@@ -4,6 +4,9 @@ namespace StabilityMatrix.Core.Models.Api;
public class CivitFile
{
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
[JsonPropertyName("sizeKB")]
public double SizeKb { get; set; }
diff --git a/StabilityMatrix.Core/Models/Api/CivitFileHashes.cs b/StabilityMatrix.Core/Models/Api/CivitFileHashes.cs
index b29628525..e299f7004 100644
--- a/StabilityMatrix.Core/Models/Api/CivitFileHashes.cs
+++ b/StabilityMatrix.Core/Models/Api/CivitFileHashes.cs
@@ -1,10 +1,18 @@
-namespace StabilityMatrix.Core.Models.Api;
+using System.Text.Json.Serialization;
-public class CivitFileHashes
+namespace StabilityMatrix.Core.Models.Api;
+
+public record CivitFileHashes
{
public string? SHA256 { get; set; }
-
+
public string? CRC32 { get; set; }
-
+
public string? BLAKE3 { get; set; }
+
+ [JsonIgnore]
+ public string ShortSha256 => SHA256?[..8] ?? string.Empty;
+
+ [JsonIgnore]
+ public string ShortBlake3 => BLAKE3?[..8] ?? string.Empty;
}
diff --git a/StabilityMatrix.Core/Models/Api/CivitFileMetadata.cs b/StabilityMatrix.Core/Models/Api/CivitFileMetadata.cs
index 81d1e8c05..5f937a4b7 100644
--- a/StabilityMatrix.Core/Models/Api/CivitFileMetadata.cs
+++ b/StabilityMatrix.Core/Models/Api/CivitFileMetadata.cs
@@ -2,7 +2,7 @@
namespace StabilityMatrix.Core.Models.Api;
-public class CivitFileMetadata
+public record CivitFileMetadata
{
[JsonPropertyName("fp")]
public string? Fp { get; set; }
diff --git a/StabilityMatrix.Core/Models/Api/CivitModelStats.cs b/StabilityMatrix.Core/Models/Api/CivitModelStats.cs
index 92559a98d..109c7585b 100644
--- a/StabilityMatrix.Core/Models/Api/CivitModelStats.cs
+++ b/StabilityMatrix.Core/Models/Api/CivitModelStats.cs
@@ -2,7 +2,7 @@
namespace StabilityMatrix.Core.Models.Api;
-public class CivitModelStats : CivitStats
+public record CivitModelStats : CivitStats
{
[JsonPropertyName("favoriteCount")]
public int FavoriteCount { get; set; }
diff --git a/StabilityMatrix.Core/Models/Api/CivitStats.cs b/StabilityMatrix.Core/Models/Api/CivitStats.cs
index e41236a1d..932a9c4aa 100644
--- a/StabilityMatrix.Core/Models/Api/CivitStats.cs
+++ b/StabilityMatrix.Core/Models/Api/CivitStats.cs
@@ -2,14 +2,14 @@
namespace StabilityMatrix.Core.Models.Api;
-public class CivitStats
+public record CivitStats
{
[JsonPropertyName("downloadCount")]
public int DownloadCount { get; set; }
-
+
[JsonPropertyName("ratingCount")]
public int RatingCount { get; set; }
-
+
[JsonPropertyName("rating")]
public double Rating { get; set; }
}
diff --git a/StabilityMatrix.Core/Models/Api/CivitTRPC/CivitImageGenerationDataResponse.cs b/StabilityMatrix.Core/Models/Api/CivitTRPC/CivitImageGenerationDataResponse.cs
new file mode 100644
index 000000000..022a41bd8
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/CivitTRPC/CivitImageGenerationDataResponse.cs
@@ -0,0 +1,105 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.CivitTRPC;
+
+public class CivitImageGenerationDataResponse
+{
+ [JsonPropertyName("process")]
+ public string? Process { get; set; }
+
+ [JsonPropertyName("meta")]
+ public CivitImageMetadata? Metadata { get; set; }
+
+ [JsonPropertyName("resources")]
+ public List? Resources { get; set; }
+
+ [JsonIgnore]
+ public IReadOnlyDictionary? OtherMetadata { get; set; }
+}
+
+public class CivitImageMetadata
+{
+ [JsonPropertyName("prompt")]
+ public string? Prompt { get; set; }
+
+ [JsonPropertyName("negativePrompt")]
+ public string? NegativePrompt { get; set; }
+
+ [JsonPropertyName("cfgScale")]
+ public double? CfgScale { get; set; }
+
+ [JsonPropertyName("steps")]
+ public int? Steps { get; set; }
+
+ [JsonPropertyName("sampler")]
+ public string? Sampler { get; set; }
+
+ [JsonPropertyName("seed")]
+ public long? Seed { get; set; }
+
+ [JsonPropertyName("Eta")]
+ public string? Eta { get; set; }
+
+ [JsonPropertyName("RNG")]
+ public string? Rng { get; set; }
+
+ [JsonPropertyName("ENSD")]
+ public string? Ensd { get; set; }
+
+ [JsonPropertyName("Size")]
+ public string? Size { get; set; }
+
+ [JsonPropertyName("width")]
+ public int? Width { get; set; }
+
+ [JsonPropertyName("height")]
+ public int? Height { get; set; }
+
+ [JsonPropertyName("Model")]
+ public string? Model { get; set; }
+
+ [JsonPropertyName("Version")]
+ public string? Version { get; set; }
+
+ [JsonPropertyName("resources")]
+ public List? Resources { get; set; }
+
+ [JsonPropertyName("ModelHash")]
+ public string? ModelHash { get; set; }
+
+ [JsonPropertyName("Hires steps")]
+ public string? HiresSteps { get; set; }
+
+ [JsonPropertyName("Hires upscale")]
+ public string? HiresUpscaleAmount { get; set; }
+
+ [JsonPropertyName("Schedule type")]
+ public string? ScheduleType { get; set; }
+
+ [JsonPropertyName("Hires upscaler")]
+ public string? HiresUpscaler { get; set; }
+
+ [JsonPropertyName("Denoising strength")]
+ public string? DenoisingStrength { get; set; }
+
+ [JsonPropertyName("clipSkip")]
+ public int? ClipSkip { get; set; }
+
+ [JsonPropertyName("scheduler")]
+ public string? Scheduler { get; set; }
+
+ [JsonIgnore]
+ public string Dimensions => string.IsNullOrWhiteSpace(Size) ? $"{Width}x{Height}" : Size;
+}
+
+public class CivitImageResource
+{
+ public string? Hash { get; set; }
+ public string? Name { get; set; }
+ public string? Type { get; set; }
+ public int ModelId { get; set; }
+ public string? ModelType { get; set; }
+ public string? ModelName { get; set; }
+ public int VersionId { get; set; }
+ public string? VersionName { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
index 24e16b6c3..a78508e8d 100644
--- a/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
+++ b/StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs
@@ -203,8 +203,8 @@ ImageNodeConnection image
Inputs = new Dictionary
{
["upscale_model"] = upscaleModel.Data,
- ["image"] = image.Data
- }
+ ["image"] = image.Data,
+ },
};
}
@@ -213,7 +213,7 @@ public static NamedComfyNode UpscaleModelLoader(stri
return new NamedComfyNode(name)
{
ClassType = "UpscaleModelLoader",
- Inputs = new Dictionary { ["model_name"] = modelName }
+ Inputs = new Dictionary { ["model_name"] = modelName },
};
}
@@ -235,8 +235,8 @@ bool crop
["upscale_method"] = method,
["height"] = height,
["width"] = width,
- ["crop"] = crop ? "center" : "disabled"
- }
+ ["crop"] = crop ? "center" : "disabled",
+ },
};
}
@@ -263,8 +263,8 @@ double strengthClip
["clip"] = clip.Data,
["lora_name"] = loraName,
["strength_model"] = strengthModel,
- ["strength_clip"] = strengthClip
- }
+ ["strength_clip"] = strengthClip,
+ },
};
}
@@ -672,10 +672,9 @@ public record LayeredDiffusionDecodeRgba : ComfyTypedNodeBase
@@ -704,10 +702,9 @@ public record SamLoader : ComfyTypedNodeBase
[TypedNodeOptions(
Name = "FaceDetailer",
- RequiredExtensions =
- [
+ RequiredExtensions = [
"https://github.com/ltdrdata/ComfyUI-Impact-Pack",
- "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
+ "https://github.com/ltdrdata/ComfyUI-Impact-Subpack",
]
)]
public record FaceDetailer : ComfyTypedNodeBase
@@ -768,7 +765,7 @@ public record FaceDetailer : ComfyTypedNodeBase
///
public required string SamMaskHintUseNegative { get; init; } = "False";
- public required StringNodeConnection Wildcard { get; init; }
+ public required string Wildcard { get; init; }
[Range(1, 32768)]
public required int DropSize { get; init; } = 10;
@@ -778,6 +775,8 @@ public record FaceDetailer : ComfyTypedNodeBase
public SamModelNodeConnection? SamModelOpt { get; set; }
public SegmDetectorNodeConnection? SegmDetectorOpt { get; set; }
+ public bool TiledEncode { get; init; }
+ public bool TiledDecode { get; init; }
}
///
@@ -1047,6 +1046,24 @@ public record PlasmaSampler : ComfyTypedNodeBase
public required LatentNodeConnection LatentImage { get; init; }
}
+ [TypedNodeOptions(
+ Name = "NRS",
+ RequiredExtensions = ["https://github.com/Reithan/negative_rejection_steering"]
+ )]
+ public record NRS : ComfyTypedNodeBase
+ {
+ public required ModelNodeConnection Model { get; init; }
+
+ [Range(-30.0f, 30.0f)]
+ public required double Skew { get; set; }
+
+ [Range(-30.0f, 30.0f)]
+ public required double Stretch { get; set; }
+
+ [Range(0f, 1f)]
+ public required double Squash { get; set; }
+ }
+
public ImageNodeConnection Lambda_LatentToImage(LatentNodeConnection latent, VAENodeConnection vae)
{
var name = GetUniqueName("VAEDecode");
@@ -1056,7 +1073,7 @@ public ImageNodeConnection Lambda_LatentToImage(LatentNodeConnection latent, VAE
{
Name = name,
Samples = latent,
- Vae = vae
+ Vae = vae,
}
)
.Output;
@@ -1071,7 +1088,7 @@ public LatentNodeConnection Lambda_ImageToLatent(ImageNodeConnection pixels, VAE
{
Name = name,
Pixels = pixels,
- Vae = vae
+ Vae = vae,
}
)
.Output;
@@ -1123,7 +1140,7 @@ int height
["height"] = height,
["crop"] = "disabled",
["samples"] = latent.Data,
- }
+ },
}
)
.Output,
@@ -1184,7 +1201,7 @@ int height
["height"] = height,
["crop"] = "disabled",
["samples"] = latent.Data,
- }
+ },
}
);
}
@@ -1197,7 +1214,7 @@ int height
{
Name = $"{name}_VAEDecode",
Samples = latent,
- Vae = vae
+ Vae = vae,
}
);
@@ -1219,7 +1236,7 @@ int height
{
Name = $"{name}_VAEEncode",
Pixels = resizedScaled.Output,
- Vae = vae
+ Vae = vae,
}
);
}
@@ -1252,7 +1269,7 @@ int height
["height"] = height,
["crop"] = "disabled",
["samples"] = latent.Data,
- }
+ },
}
);
@@ -1262,7 +1279,7 @@ int height
{
Name = $"{name}_VAEDecode",
Samples = latentUpscale.Output,
- Vae = vae
+ Vae = vae,
}
);
}
@@ -1275,7 +1292,7 @@ int height
{
Name = $"{name}_VAEDecode",
Samples = latent,
- Vae = vae
+ Vae = vae,
}
);
@@ -1322,7 +1339,7 @@ int height
["width"] = width,
["height"] = height,
["crop"] = "disabled",
- }
+ },
}
);
}
diff --git a/StabilityMatrix.Core/Models/Api/HuggingFace/HuggingFaceUser.cs b/StabilityMatrix.Core/Models/Api/HuggingFace/HuggingFaceUser.cs
new file mode 100644
index 000000000..e1cab8db8
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/HuggingFace/HuggingFaceUser.cs
@@ -0,0 +1,9 @@
+using System.Text.Json.Serialization;
+
+namespace StabilityMatrix.Core.Models.Api.HuggingFace;
+
+public record HuggingFaceUser
+{
+ [JsonPropertyName("name")]
+ public string? Name { get; init; }
+}
diff --git a/StabilityMatrix.Core/Models/Api/HuggingFaceAccountStatusUpdateEventArgs.cs b/StabilityMatrix.Core/Models/Api/HuggingFaceAccountStatusUpdateEventArgs.cs
new file mode 100644
index 000000000..4b0199a28
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Api/HuggingFaceAccountStatusUpdateEventArgs.cs
@@ -0,0 +1,20 @@
+namespace StabilityMatrix.Core.Models.Api; // Or StabilityMatrix.Core.Models.Api.HuggingFace
+
+public class HuggingFaceAccountStatusUpdateEventArgs : EventArgs
+{
+ public bool IsConnected { get; init; }
+ public string? Username { get; init; } // Optional: if we decide to fetch/display username
+ public string? ErrorMessage { get; init; }
+
+ public static HuggingFaceAccountStatusUpdateEventArgs Disconnected => new(false, null);
+
+ // Constructor to allow initialization, matching the usage in AccountSettingsViewModel
+ public HuggingFaceAccountStatusUpdateEventArgs() { }
+
+ public HuggingFaceAccountStatusUpdateEventArgs(bool isConnected, string? username, string? errorMessage = null)
+ {
+ IsConnected = isConnected;
+ Username = username;
+ ErrorMessage = errorMessage;
+ }
+}
diff --git a/StabilityMatrix.Core/Models/CivitaiResource.cs b/StabilityMatrix.Core/Models/CivitaiResource.cs
new file mode 100644
index 000000000..4b66e9ccc
--- /dev/null
+++ b/StabilityMatrix.Core/Models/CivitaiResource.cs
@@ -0,0 +1,10 @@
+namespace StabilityMatrix.Core.Models;
+
+public class CivitaiResource
+{
+ public string Type { get; set; }
+ public int ModelVersionId { get; set; }
+ public string ModelName { get; set; }
+ public string ModelVersionName { get; set; }
+ public double? Weight { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/ConnectedModelInfo.cs b/StabilityMatrix.Core/Models/ConnectedModelInfo.cs
index 4c23cc943..c634f4476 100644
--- a/StabilityMatrix.Core/Models/ConnectedModelInfo.cs
+++ b/StabilityMatrix.Core/Models/ConnectedModelInfo.cs
@@ -5,7 +5,7 @@
namespace StabilityMatrix.Core.Models;
-public class ConnectedModelInfo
+public class ConnectedModelInfo : IEquatable
{
[JsonIgnore]
public const string FileExtension = ".cm-info.json";
@@ -29,6 +29,7 @@ public class ConnectedModelInfo
// User settings
public string? UserTitle { get; set; }
public string? ThumbnailImageUrl { get; set; }
+ public InferenceDefaults? InferenceDefaults { get; set; }
public ConnectedModelSource? Source { get; set; } = ConnectedModelSource.Civitai;
@@ -59,6 +60,33 @@ DateTimeOffset importedAt
Source = ConnectedModelSource.Civitai;
}
+ public ConnectedModelInfo(
+ CivitModel civitModel,
+ CivitModelVersion civitModelVersion,
+ CivitFile civitFile,
+ DateTimeOffset importedAt,
+ InferenceDefaults? inferenceDefaults
+ )
+ {
+ ModelId = civitModel.Id;
+ ModelName = civitModel.Name;
+ ModelDescription = civitModel.Description ?? string.Empty;
+ Nsfw = civitModel.Nsfw;
+ Tags = civitModel.Tags;
+ ModelType = civitModel.Type;
+ VersionId = civitModelVersion.Id;
+ VersionName = civitModelVersion.Name;
+ VersionDescription = civitModelVersion.Description;
+ ImportedAt = importedAt;
+ BaseModel = civitModelVersion.BaseModel;
+ FileMetadata = civitFile.Metadata;
+ Hashes = civitFile.Hashes;
+ TrainedWords = civitModelVersion.TrainedWords;
+ Stats = civitModel.Stats;
+ Source = ConnectedModelSource.Civitai;
+ InferenceDefaults = inferenceDefaults;
+ }
+
public ConnectedModelInfo(
OpenModelDbKeyedModel model,
OpenModelDbResource resource,
@@ -99,6 +127,104 @@ public async Task SaveJsonToDirectory(string directoryPath, string modelFileName
[JsonIgnore]
public string TrainedWordsString => TrainedWords != null ? string.Join(", ", TrainedWords) : string.Empty;
+
+ public bool Equals(ConnectedModelInfo? other)
+ {
+ if (other is null)
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
+ return Comparer.Equals(this, other);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is null)
+ return false;
+ if (ReferenceEquals(this, obj))
+ return true;
+ if (obj.GetType() != GetType())
+ return false;
+ return Equals((ConnectedModelInfo)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return Comparer.GetHashCode(this);
+ }
+
+ public static bool operator ==(ConnectedModelInfo? left, ConnectedModelInfo? right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(ConnectedModelInfo? left, ConnectedModelInfo? right)
+ {
+ return !Equals(left, right);
+ }
+
+ private sealed class ConnectedModelInfoEqualityComparer : IEqualityComparer
+ {
+ public bool Equals(ConnectedModelInfo? x, ConnectedModelInfo? y)
+ {
+ if (ReferenceEquals(x, y))
+ return true;
+ if (x is null)
+ return false;
+ if (y is null)
+ return false;
+ if (x.GetType() != y.GetType())
+ return false;
+
+ return x.ModelId == y.ModelId
+ && x.ModelName == y.ModelName
+ && x.ModelDescription == y.ModelDescription
+ && x.Nsfw == y.Nsfw
+ && x.Tags?.SequenceEqual(y.Tags ?? []) is null or true
+ && x.ModelType == y.ModelType
+ && x.VersionId == y.VersionId
+ && x.VersionName == y.VersionName
+ && x.VersionDescription == y.VersionDescription
+ && x.BaseModel == y.BaseModel
+ && x.FileMetadata == y.FileMetadata
+ && x.ImportedAt.Equals(y.ImportedAt)
+ && x.Hashes == y.Hashes
+ && x.TrainedWords?.SequenceEqual(y.TrainedWords ?? []) is null or true
+ && x.Stats == y.Stats
+ && x.UserTitle == y.UserTitle
+ && x.ThumbnailImageUrl == y.ThumbnailImageUrl
+ && x.InferenceDefaults == y.InferenceDefaults
+ && x.Source == y.Source;
+ }
+
+ public int GetHashCode(ConnectedModelInfo obj)
+ {
+ var hashCode = new HashCode();
+ hashCode.Add(obj.ModelId);
+ hashCode.Add(obj.ModelName);
+ hashCode.Add(obj.ModelDescription);
+ hashCode.Add(obj.Nsfw);
+ hashCode.Add(obj.Tags);
+ hashCode.Add((int)obj.ModelType);
+ hashCode.Add(obj.VersionId);
+ hashCode.Add(obj.VersionName);
+ hashCode.Add(obj.VersionDescription);
+ hashCode.Add(obj.BaseModel);
+ hashCode.Add(obj.FileMetadata);
+ hashCode.Add(obj.ImportedAt);
+ hashCode.Add(obj.Hashes);
+ hashCode.Add(obj.TrainedWords);
+ hashCode.Add(obj.Stats);
+ hashCode.Add(obj.UserTitle);
+ hashCode.Add(obj.ThumbnailImageUrl);
+ hashCode.Add(obj.InferenceDefaults);
+ hashCode.Add(obj.Source);
+ return hashCode.ToHashCode();
+ }
+ }
+
+ public static IEqualityComparer Comparer { get; } =
+ new ConnectedModelInfoEqualityComparer();
}
[JsonSourceGenerationOptions(
diff --git a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
index 1f257318f..0dcbe9281 100644
--- a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
+++ b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs
@@ -43,7 +43,7 @@ public virtual bool Equals(LocalModelFile? other)
if (ReferenceEquals(this, other))
return true;
return RelativePath == other.RelativePath
- && Equals(ConnectedModelInfo, other.ConnectedModelInfo)
+ && ConnectedModelInfo == other.ConnectedModelInfo
&& HasUpdate == other.HasUpdate;
}
@@ -223,7 +223,7 @@ public IEnumerable GetDeleteFullPaths(string rootModelDirectory)
".pth",
".bin",
".sft",
- ".gguf"
+ ".gguf",
];
public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".webp"];
public static readonly HashSet SupportedMetadataExtensions = [".json"];
diff --git a/StabilityMatrix.Core/Models/DimensionStringComparer.cs b/StabilityMatrix.Core/Models/DimensionStringComparer.cs
new file mode 100644
index 000000000..64a718ddc
--- /dev/null
+++ b/StabilityMatrix.Core/Models/DimensionStringComparer.cs
@@ -0,0 +1,79 @@
+using System.Collections;
+using System.Text.RegularExpressions;
+
+namespace StabilityMatrix.Core.Models;
+
+public partial class DimensionStringComparer : IComparer, IComparer
+{
+ public static readonly DimensionStringComparer Instance = new();
+
+ ///
+ /// Compares two dimension strings (like "1024 x 768") by the first numeric value.
+ ///
+ /// First dimension string to compare
+ /// Second dimension string to compare
+ ///
+ /// A negative value if x comes before y;
+ /// zero if x equals y;
+ /// a positive value if x comes after y
+ ///
+ public int Compare(object? x, object? y)
+ {
+ // Handle null cases
+ if (x == null && y == null)
+ return 0;
+ if (x == null)
+ return -1;
+ if (y == null)
+ return 1;
+
+ if (x is not string xStr || y is not string yStr)
+ throw new ArgumentException("Both arguments must be strings.");
+
+ // Extract the first number from each string
+ var firstX = ExtractFirstNumber(xStr);
+ var firstY = ExtractFirstNumber(yStr);
+
+ // Compare the first numbers
+ return firstX.CompareTo(firstY);
+ }
+
+ public int Compare(string? x, string? y)
+ {
+ // Handle null cases
+ if (x == null && y == null)
+ return 0;
+ if (x == null)
+ return -1;
+ if (y == null)
+ return 1;
+
+ // Extract the first number from each string
+ var firstX = ExtractFirstNumber(x);
+ var firstY = ExtractFirstNumber(y);
+
+ // Compare the first numbers
+ return firstX.CompareTo(firstY);
+ }
+
+ ///
+ /// Extracts the first numeric value from a dimension string.
+ ///
+ /// String in format like "1024 x 768"
+ /// The first numeric value or 0 if no number is found
+ private static int ExtractFirstNumber(string dimensionString)
+ {
+ // Use regex to find the first number in the string
+ var match = NumberRegex().Match(dimensionString);
+
+ if (match.Success && int.TryParse(match.Value, out var result))
+ {
+ return result;
+ }
+
+ return 0; // Return 0 if no number found
+ }
+
+ [GeneratedRegex(@"\d+")]
+ private static partial Regex NumberRegex();
+}
diff --git a/StabilityMatrix.Core/Models/GenerationParameters.cs b/StabilityMatrix.Core/Models/GenerationParameters.cs
index adeb1924b..e4f7d2073 100644
--- a/StabilityMatrix.Core/Models/GenerationParameters.cs
+++ b/StabilityMatrix.Core/Models/GenerationParameters.cs
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
using System.Text.Json.Serialization;
using StabilityMatrix.Core.Models.Api.Comfy;
@@ -27,6 +28,13 @@ public record GenerationParameters
public double MinCfg { get; set; }
public double AugmentationLevel { get; set; }
public string? VideoOutputMethod { get; set; }
+ public int? ModelVersionId { get; set; }
+ public List? ExtraNetworkModelVersionIds { get; set; }
+
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ };
public static bool TryParse(
string? text,
@@ -101,6 +109,34 @@ public static GenerationParameters Parse(string text)
ModelName = lineFields.GetValueOrDefault("Model"),
};
+ if (lineFields.ContainsKey("Civitai resources"))
+ {
+ // [{"type":"checkpoint","modelVersionId":290640,"modelName":"Pony Diffusion V6 XL","modelVersionName":"V6 (start with this one)"},{"type":"lora","weight":0.8,"modelVersionId":333590,"modelName":"Not Artists Styles for Pony Diffusion V6 XL","modelVersionName":"Anime 2"}]
+ var civitaiResources = lineFields["Civitai resources"];
+ if (!string.IsNullOrWhiteSpace(civitaiResources))
+ {
+ var resources = JsonSerializer.Deserialize>(
+ civitaiResources,
+ JsonOptions
+ );
+ if (resources is not null)
+ {
+ generationParameters.ModelName ??= resources
+ .FirstOrDefault(x => x.Type == "checkpoint")
+ ?.ModelName;
+ generationParameters.ModelVersionId ??= resources
+ .FirstOrDefault(x => x.Type == "checkpoint")
+ ?.ModelVersionId;
+
+ foreach (var lora in resources.Where(x => x.Type == "lora"))
+ {
+ generationParameters.ExtraNetworkModelVersionIds ??= [];
+ generationParameters.ExtraNetworkModelVersionIds.Add(lora.ModelVersionId);
+ }
+ }
+ }
+ }
+
if (lineFields.GetValueOrDefault("Size") is { } size)
{
var split = size.Split('x', 2);
@@ -109,7 +145,7 @@ public static GenerationParameters Parse(string text)
generationParameters = generationParameters with
{
Width = int.Parse(split[0]),
- Height = int.Parse(split[1])
+ Height = int.Parse(split[1]),
};
}
}
@@ -289,7 +325,7 @@ _ when source.StartsWith("DPM++ 3M SDE") => ComfySampler.Dpmpp3MSde,
_ when source.StartsWith("DPM++ 3M") => ComfySampler.Dpmpp3M,
_ when source.StartsWith("DPM++ SDE") => ComfySampler.DpmppSde,
_ when source.StartsWith("DPM++ 2S a") => ComfySampler.Dpmpp2SAncestral,
- _ => default
+ _ => default,
};
return (sampler, scheduler);
@@ -311,7 +347,7 @@ public static GenerationParameters GetSample()
Seed = 124825529,
ModelName = "ExampleMix7",
ModelHash = "b899d188a1ac7356bfb9399b2277d5b21712aa360f8f9514fba6fcce021baff7",
- Sampler = "DPM++ 2M Karras"
+ Sampler = "DPM++ 2M Karras",
};
}
}
diff --git a/StabilityMatrix.Core/Models/HybridModelFile.cs b/StabilityMatrix.Core/Models/HybridModelFile.cs
index da51bbe3e..9348a6d15 100644
--- a/StabilityMatrix.Core/Models/HybridModelFile.cs
+++ b/StabilityMatrix.Core/Models/HybridModelFile.cs
@@ -132,6 +132,11 @@ public string GetId()
return $"{RelativePath.NormalizePathSeparators()};{IsNone};{IsDefault}";
}
+ ///
+ /// Special Comparer that compares Remote Name and Local RelativePath,
+ /// used for letting remote models not override local models with more metadata.
+ /// Pls do not use for other stuff.
+ ///
private sealed class RemoteNameLocalEqualityComparer : IEqualityComparer
{
public bool Equals(HybridModelFile? x, HybridModelFile? y)
@@ -151,7 +156,6 @@ public bool Equals(HybridModelFile? x, HybridModelFile? y)
// This equality affects replacements of remote over local models
// We want local and remote models to be considered equal if they have the same relative path
// But 2 local models with the same path but different config paths should be considered different
-
return !(x.Type == y.Type && x.Local?.ConfigFullPath != y.Local?.ConfigFullPath);
}
@@ -161,6 +165,45 @@ public int GetHashCode(HybridModelFile obj)
}
}
+ ///
+ /// Actual general purpose equality comparer.
+ /// Use this for general equality checks :)
+ ///
+ private sealed class EqualityComparer : IEqualityComparer
+ {
+ public bool Equals(HybridModelFile? x, HybridModelFile? y)
+ {
+ if (ReferenceEquals(x, y))
+ return true;
+ if (ReferenceEquals(x, null))
+ return false;
+ if (ReferenceEquals(y, null))
+ return false;
+ if (x.GetType() != y.GetType())
+ return false;
+
+ if (!Equals(x.RelativePath.NormalizePathSeparators(), y.RelativePath.NormalizePathSeparators()))
+ return false;
+
+ return Equals(x.Type, y.Type)
+ && x.RemoteName == y.RemoteName
+ && x.Local?.ConfigFullPath == y.Local?.ConfigFullPath
+ && x.Local?.ConnectedModelInfo == y.Local?.ConnectedModelInfo;
+ }
+
+ public int GetHashCode(HybridModelFile obj)
+ {
+ return HashCode.Combine(
+ obj.IsNone,
+ obj.IsDefault,
+ obj.RelativePath,
+ obj.RemoteName,
+ obj.Local?.ConfigFullPath,
+ obj.Local?.ConnectedModelInfo
+ );
+ }
+ }
+
///
/// Whether this instance is the default model.
///
@@ -171,7 +214,18 @@ public int GetHashCode(HybridModelFile obj)
///
public bool IsNone => ReferenceEquals(this, None);
- public static IEqualityComparer Comparer { get; } =
+ ///
+ /// Actual general purpose equality comparer.
+ /// Use this for general equality checks :)
+ ///
+ public static IEqualityComparer Comparer { get; } = new EqualityComparer();
+
+ ///
+ /// Special Comparer that compares Remote Name and Local RelativePath,
+ /// used for letting remote models not override local models with more metadata.
+ /// Pls do not use for other stuff.
+ ///
+ public static IEqualityComparer RemoteLocalComparer { get; } =
new RemoteNameLocalEqualityComparer();
[JsonIgnore]
diff --git a/StabilityMatrix.Core/Models/InferenceDefaults.cs b/StabilityMatrix.Core/Models/InferenceDefaults.cs
new file mode 100644
index 000000000..2bceaeae3
--- /dev/null
+++ b/StabilityMatrix.Core/Models/InferenceDefaults.cs
@@ -0,0 +1,13 @@
+using StabilityMatrix.Core.Models.Api.Comfy;
+
+namespace StabilityMatrix.Core.Models;
+
+public record InferenceDefaults
+{
+ public ComfySampler? Sampler { get; set; }
+ public ComfyScheduler? Scheduler { get; set; }
+ public int Steps { get; set; }
+ public double CfgScale { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/InstalledPackage.cs b/StabilityMatrix.Core/Models/InstalledPackage.cs
index 74987a5b8..21fedfe10 100644
--- a/StabilityMatrix.Core/Models/InstalledPackage.cs
+++ b/StabilityMatrix.Core/Models/InstalledPackage.cs
@@ -55,6 +55,8 @@ public class InstalledPackage : IJsonOnDeserialized
public List? PipOverrides { get; set; }
+ public string PythonVersion { get; set; } = PyInstallationManager.Python_3_10_11.StringValue;
+
///
/// Get the launch args host option value.
///
diff --git a/StabilityMatrix.Core/Models/ObservableHashSet.cs b/StabilityMatrix.Core/Models/ObservableHashSet.cs
new file mode 100644
index 000000000..38a39cfbc
--- /dev/null
+++ b/StabilityMatrix.Core/Models/ObservableHashSet.cs
@@ -0,0 +1,201 @@
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+
+namespace StabilityMatrix.Core.Models;
+
+///
+/// Represents a observable hash set of values.
+///
+/// The type of elements in the hash set.
+///
+/// An that also implements –
+/// perfect when you need *unique* items **and** change notifications
+/// (e.g. for XAML bindings or ReactiveUI/DynamicData pipelines).
+///
+public class ObservableHashSet : ObservableCollection, ISet
+{
+ private readonly HashSet set;
+
+ #region ░░░ Constructors ░░░
+
+ public ObservableHashSet()
+ : this((IEqualityComparer?)null) { }
+
+ public ObservableHashSet(IEqualityComparer? comparer)
+ : base()
+ {
+ set = new HashSet(comparer ?? EqualityComparer.Default);
+ }
+
+ public ObservableHashSet(IEnumerable collection)
+ : this(collection, null) { }
+
+ public ObservableHashSet(IEnumerable collection, IEqualityComparer? comparer)
+ : this(comparer)
+ {
+ foreach (var item in collection)
+ Add(item); // guarantees uniqueness + raises events
+ }
+
+ #endregion
+
+ #region ░░░ Overrides that enforce set semantics ░░░
+
+ ///
+ /// Called by (and therefore by LINQ’s
+ /// Add extension, by , etc.).
+ /// We only insert if the value was *not* already present in the set.
+ ///
+ protected override void InsertItem(int index, T item)
+ {
+ if (set.Add(item)) // true == unique
+ base.InsertItem(index, item); // fires events
+ // duplicate → ignore silently (same behaviour as HashSet)
+ }
+
+ protected override void SetItem(int index, T item)
+ {
+ var existing = this[index];
+
+ // no-op if same reference/value
+ if (EqualityComparer.Default.Equals(existing, item))
+ return;
+
+ // attempting to “replace” with a value already in the set → ignore
+ if (set.Contains(item))
+ return;
+
+ set.Remove(existing);
+ set.Add(item);
+
+ base.SetItem(index, item); // fires events
+ }
+
+ protected override void RemoveItem(int index)
+ {
+ set.Remove(this[index]);
+ base.RemoveItem(index); // fires events
+ }
+
+ protected override void ClearItems()
+ {
+ set.Clear();
+ base.ClearItems(); // fires events
+ }
+
+ #endregion
+
+ #region ░░░ ISet explicit implementation ░░░
+
+ // Most operations delegate to the HashSet for the heavy lifting,
+ // THEN synchronise the ObservableCollection so that UI bindings
+ // get the right notifications.
+
+ bool ISet.Add(T item) => !set.Contains(item) && AddAndReturnTrue(item);
+
+ private bool AddAndReturnTrue(T item)
+ {
+ base.Add(item);
+ return true;
+ }
+
+ void ISet.ExceptWith(IEnumerable other)
+ {
+ ArgumentNullException.ThrowIfNull(other);
+ foreach (var item in other)
+ _ = Remove(item); // Remove() already updates both collections
+ }
+
+ void ISet.IntersectWith(IEnumerable other)
+ {
+ ArgumentNullException.ThrowIfNull(other);
+ var keep = new HashSet(other, set.Comparer);
+ for (var i = Count - 1; i >= 0; i--)
+ if (!keep.Contains(this[i]))
+ RemoveItem(i);
+ }
+
+ void ISet.SymmetricExceptWith(IEnumerable other)
+ {
+ ArgumentNullException.ThrowIfNull(other);
+ var toToggle = new HashSet(other, set.Comparer);
+
+ foreach (var item in toToggle)
+ if (!Remove(item))
+ Add(item);
+ }
+
+ void ISet.UnionWith(IEnumerable other)
+ {
+ ArgumentNullException.ThrowIfNull(other);
+ foreach (var item in other)
+ _ = ((ISet)this).Add(item); // uses our Add logic
+ }
+
+ // The pure-query methods just delegate to HashSet:
+
+ bool ISet.IsSubsetOf(IEnumerable other) => set.IsSubsetOf(other);
+
+ bool ISet.IsSupersetOf(IEnumerable other) => set.IsSupersetOf(other);
+
+ bool ISet.IsProperSubsetOf(IEnumerable other) => set.IsProperSubsetOf(other);
+
+ bool ISet.IsProperSupersetOf(IEnumerable other) => set.IsProperSupersetOf(other);
+
+ bool ISet.Overlaps(IEnumerable other) => set.Overlaps(other);
+
+ public bool SetEquals(IEnumerable other) => set.SetEquals(other);
+
+ #endregion
+
+ #region ░░░ Useful helpers ░░░
+
+ public new bool Contains(T item) => set.Contains(item);
+
+ ///
+ /// Returns a copy of the internal .
+ /// Handy when you need fast look-ups without exposing mutability.
+ ///
+ public HashSet ToHashSet() => new(set, set.Comparer);
+
+ public void AddRange(IEnumerable items)
+ {
+ ArgumentNullException.ThrowIfNull(items);
+
+ // 1. Keep only values that are truly new for this set
+ var newItems = new List();
+ foreach (var item in items)
+ if (set.Add(item)) // true == not yet present
+ newItems.Add(item);
+
+ if (newItems.Count == 0)
+ return; // nothing to do
+
+ CheckReentrancy(); // ObservableCollection helper
+
+ // 2. Append to the internal Items list *without* raising events yet
+ int startIdx = Items.Count;
+ foreach (var item in newItems)
+ Items.Add(item);
+
+ // 3. Fire a single consolidated notification
+ OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
+
+ // choose either a single "Add" with the batch…
+ OnCollectionChanged(
+ new NotifyCollectionChangedEventArgs(
+ NotifyCollectionChangedAction.Add,
+ newItems, // the items added
+ startIdx
+ )
+ ); // starting index
+
+ // …or, if you want absolute safety for all consumers,
+ // you could raise "Reset" instead:
+ // OnCollectionChanged(new NotifyCollectionChangedEventArgs(
+ // NotifyCollectionChangedAction.Reset));
+ }
+
+ #endregion
+}
diff --git a/StabilityMatrix.Core/Models/PackageModification/InstallNunchakuStep.cs b/StabilityMatrix.Core/Models/PackageModification/InstallNunchakuStep.cs
new file mode 100644
index 000000000..3b450b033
--- /dev/null
+++ b/StabilityMatrix.Core/Models/PackageModification/InstallNunchakuStep.cs
@@ -0,0 +1,121 @@
+using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Helper.HardwareInfo;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Models.Packages.Extensions;
+using StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Python;
+
+namespace StabilityMatrix.Core.Models.PackageModification;
+
+public class InstallNunchakuStep(IPyInstallationManager pyInstallationManager) : IPackageStep
+{
+ public required InstalledPackage InstalledPackage { get; init; }
+ public required DirectoryPath WorkingDirectory { get; init; }
+ public required GpuInfo? PreferredGpu { get; init; }
+ public required IPackageExtensionManager ComfyExtensionManager { get; init; }
+ public IReadOnlyDictionary? EnvironmentVariables { get; init; }
+
+ public async Task ExecuteAsync(IProgress? progress = null)
+ {
+ if (Compat.IsMacOS || PreferredGpu?.ComputeCapabilityValue is null or < 7.5m)
+ {
+ throw new NotSupportedException(
+ "Nunchaku is not supported on macOS or GPUs with compute capability < 7.5."
+ );
+ }
+
+ var venvDir = WorkingDirectory.JoinDir("venv");
+ var pyVersion = PyVersion.Parse(InstalledPackage.PythonVersion);
+ if (pyVersion.StringValue == "0.0.0")
+ {
+ pyVersion = PyInstallationManager.Python_3_10_11;
+ }
+
+ var baseInstall = !string.IsNullOrWhiteSpace(InstalledPackage.PythonVersion)
+ ? new PyBaseInstall(
+ await pyInstallationManager.GetInstallationAsync(pyVersion).ConfigureAwait(false)
+ )
+ : PyBaseInstall.Default;
+
+ await using var venvRunner = baseInstall.CreateVenvRunner(
+ venvDir,
+ workingDirectory: WorkingDirectory,
+ environmentVariables: EnvironmentVariables
+ );
+
+ var torchInfo = await venvRunner.PipShow("torch").ConfigureAwait(false);
+ var shortPythonVersionString = pyVersion.Minor switch
+ {
+ 10 => "cp310",
+ 11 => "cp311",
+ 12 => "cp312",
+ _ => throw new ArgumentOutOfRangeException("Invalid Python version"),
+ };
+ var platform = Compat.IsWindows ? "win_amd64" : "linux_x86_64";
+
+ if (torchInfo is null)
+ {
+ throw new InvalidOperationException("Torch is not installed in the virtual environment.");
+ }
+
+ var torchVersion = torchInfo.Version switch
+ {
+ var v when v.StartsWith("2.5") => "2.5",
+ var v when v.StartsWith("2.6") => "2.6",
+ var v when v.StartsWith("2.7") => "2.7",
+ var v when v.StartsWith("2.8") => "2.8",
+ _ => throw new InvalidOperationException(
+ "No compatible torch version found in the virtual environment."
+ ),
+ };
+
+ var downloadUrl =
+ $"https://github.com/mit-han-lab/nunchaku/releases/download/v0.3.1/nunchaku-0.3.1+torch{torchVersion}-{shortPythonVersionString}-{shortPythonVersionString}-{platform}.whl";
+ progress?.Report(
+ new ProgressReport(-1f, message: "Installing Nunchaku backend", isIndeterminate: true)
+ );
+
+ await venvRunner.PipInstall(downloadUrl, progress.AsProcessOutputHandler()).ConfigureAwait(false);
+
+ progress?.Report(
+ new ProgressReport(1f, message: "Nunchaku backend installed successfully", isIndeterminate: false)
+ );
+
+ var nunchakuNodePath = WorkingDirectory.JoinDir("custom_nodes", "ComfyUI-nunchaku");
+ if (nunchakuNodePath.Exists)
+ {
+ progress?.Report(
+ new ProgressReport(
+ 1f,
+ message: "Nunchaku extension installed successfully.",
+ isIndeterminate: false
+ )
+ );
+ return;
+ }
+
+ var extensions = await ComfyExtensionManager
+ .GetManifestExtensionsAsync(ComfyExtensionManager.DefaultManifests)
+ .ConfigureAwait(false);
+ var nunchakuExtension = extensions.FirstOrDefault(e =>
+ e.Title.Equals("ComfyUI-nunchaku", StringComparison.OrdinalIgnoreCase)
+ );
+ if (nunchakuExtension is null)
+ return;
+
+ await ComfyExtensionManager
+ .InstallExtensionAsync(nunchakuExtension, InstalledPackage, null, progress)
+ .ConfigureAwait(false);
+
+ progress?.Report(
+ new ProgressReport(
+ 1f,
+ message: "Nunchaku extension installed successfully.",
+ isIndeterminate: false
+ )
+ );
+ }
+
+ public string ProgressTitle => "Installing nunchaku...";
+}
diff --git a/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs b/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs
index 58cb8c1c7..202a3cfdb 100644
--- a/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs
+++ b/StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs
@@ -11,7 +11,8 @@ namespace StabilityMatrix.Core.Models.PackageModification;
public class InstallSageAttentionStep(
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
) : IPackageStep
{
private const string PythonLibsDownloadUrl = "https://cdn.lykos.ai/python_libs_for_sage.zip";
@@ -31,8 +32,19 @@ public async Task ExecuteAsync(IProgress? progress = null)
}
var venvDir = WorkingDirectory.JoinDir("venv");
+ var pyVersion = PyVersion.Parse(InstalledPackage.PythonVersion);
+ if (pyVersion.StringValue == "0.0.0")
+ {
+ pyVersion = PyInstallationManager.Python_3_10_11;
+ }
+
+ var baseInstall = !string.IsNullOrWhiteSpace(InstalledPackage.PythonVersion)
+ ? new PyBaseInstall(
+ await pyInstallationManager.GetInstallationAsync(pyVersion).ConfigureAwait(false)
+ )
+ : PyBaseInstall.Default;
- await using var venvRunner = PyBaseInstall.Default.CreateVenvRunner(
+ await using var venvRunner = baseInstall.CreateVenvRunner(
venvDir,
workingDirectory: WorkingDirectory,
environmentVariables: EnvironmentVariables
@@ -40,6 +52,13 @@ public async Task ExecuteAsync(IProgress? progress = null)
var torchInfo = await venvRunner.PipShow("torch").ConfigureAwait(false);
var sageWheelUrl = string.Empty;
+ var shortPythonVersionString = pyVersion.Minor switch
+ {
+ 10 => "cp310",
+ 11 => "cp311",
+ 12 => "cp312",
+ _ => throw new ArgumentOutOfRangeException("Invalid Python version"),
+ };
if (torchInfo == null)
{
@@ -48,27 +67,27 @@ public async Task ExecuteAsync(IProgress? progress = null)
else if (torchInfo.Version.Contains("2.5.1") && torchInfo.Version.Contains("cu124"))
{
sageWheelUrl =
- "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu124torch2.5.1-cp310-cp310-win_amd64.whl";
+ "https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post2/sageattention-2.2.0+cu124torch2.5.1.post2-cp39-abi3-win_amd64.whl";
}
else if (torchInfo.Version.Contains("2.6.0") && torchInfo.Version.Contains("cu126"))
{
sageWheelUrl =
- "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu126torch2.6.0-cp310-cp310-win_amd64.whl";
+ $"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post2/sageattention-2.2.0+cu126torch2.6.0.post2-cp39-abi3-win_amd64.whl";
}
else if (torchInfo.Version.Contains("2.7.0") && torchInfo.Version.Contains("cu128"))
{
sageWheelUrl =
- "https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu128torch2.7.0-cp310-cp310-win_amd64.whl";
+ $"https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu128torch2.7.0-{shortPythonVersionString}-{shortPythonVersionString}-win_amd64.whl";
}
else if (torchInfo.Version.Contains("2.7.1") && torchInfo.Version.Contains("cu128"))
{
sageWheelUrl =
- $"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows/sageattention-2.2.0+cu128torch2.7.1-cp310-cp310-win_amd64.whl";
+ $"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post2/sageattention-2.2.0+cu128torch2.7.1.post2-cp39-abi3-win_amd64.whl";
}
else if (torchInfo.Version.Contains("2.8.0") && torchInfo.Version.Contains("cu128"))
{
sageWheelUrl =
- $"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows/sageattention-2.2.0+cu128torch2.8.0-cp310-cp310-win_amd64.whl";
+ $"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post2/sageattention-2.2.0+cu128torch2.8.0.post2-cp39-abi3-win_amd64.whl";
}
var pipArgs = new PipInstallArgs();
@@ -85,7 +104,9 @@ public async Task ExecuteAsync(IProgress? progress = null)
progress?.Report(
new ProgressReport(-1f, message: "Installing Triton & SageAttention", isIndeterminate: true)
);
+
await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false);
+
return;
}
diff --git a/StabilityMatrix.Core/Models/PackageModification/PipStep.cs b/StabilityMatrix.Core/Models/PackageModification/PipStep.cs
index 2db356c23..254cf3217 100644
--- a/StabilityMatrix.Core/Models/PackageModification/PipStep.cs
+++ b/StabilityMatrix.Core/Models/PackageModification/PipStep.cs
@@ -15,6 +15,8 @@ public class PipStep : IPackageStep
public IReadOnlyDictionary? EnvironmentVariables { get; init; }
+ public PyBaseInstall? BaseInstall { get; set; }
+
///
public string ProgressTitle =>
Args switch
@@ -28,13 +30,22 @@ _ when Args.Contains("-U") || Args.Contains("--upgrade") => "Updating Pip Packag
///
public async Task ExecuteAsync(IProgress? progress = null)
{
- await using var venvRunner = PyBaseInstall.Default.CreateVenvRunner(
+ BaseInstall ??= PyBaseInstall.Default;
+ await using var venvRunner = BaseInstall.CreateVenvRunner(
VenvDirectory,
workingDirectory: WorkingDirectory,
environmentVariables: EnvironmentVariables
);
- venvRunner.RunDetached(Args.Prepend(["-m", "pip"]), progress.AsProcessOutputHandler());
+ if (BaseInstall.UsesUv && Args.Contains("install"))
+ {
+ var uvArgs = Args.ToString().Replace("install ", string.Empty);
+ await venvRunner.PipInstall(uvArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false);
+ }
+ else
+ {
+ venvRunner.RunDetached(Args.Prepend(["-m", "pip"]), progress.AsProcessOutputHandler());
+ }
await ProcessRunner.WaitForExitConditionAsync(venvRunner.Process).ConfigureAwait(false);
}
diff --git a/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs b/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs
index 107e75a79..de2e96c16 100644
--- a/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs
+++ b/StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs
@@ -5,27 +5,17 @@
namespace StabilityMatrix.Core.Models.PackageModification;
-public class SetupPrerequisitesStep : IPackageStep
+public class SetupPrerequisitesStep(
+ IPrerequisiteHelper prerequisiteHelper,
+ BasePackage package,
+ PyVersion? pythonVersion = null
+) : IPackageStep
{
- private readonly IPrerequisiteHelper prerequisiteHelper;
- private readonly IPyRunner pyRunner;
- private readonly BasePackage package;
-
- public SetupPrerequisitesStep(
- IPrerequisiteHelper prerequisiteHelper,
- IPyRunner pyRunner,
- BasePackage package
- )
- {
- this.prerequisiteHelper = prerequisiteHelper;
- this.pyRunner = pyRunner;
- this.package = package;
- }
-
public async Task ExecuteAsync(IProgress? progress = null)
{
- // package and platform-specific requirements install
- await prerequisiteHelper.InstallPackageRequirements(package, progress).ConfigureAwait(false);
+ await prerequisiteHelper
+ .InstallPackageRequirements(package, pythonVersion, progress)
+ .ConfigureAwait(false);
}
public string ProgressTitle => "Installing prerequisites...";
diff --git a/StabilityMatrix.Core/Models/PackagePrerequisite.cs b/StabilityMatrix.Core/Models/PackagePrerequisite.cs
index 614fed611..0c5d18248 100644
--- a/StabilityMatrix.Core/Models/PackagePrerequisite.cs
+++ b/StabilityMatrix.Core/Models/PackagePrerequisite.cs
@@ -3,6 +3,7 @@
public enum PackagePrerequisite
{
Python310,
+ Python31017,
VcRedist,
Git,
HipSdk,
diff --git a/StabilityMatrix.Core/Models/PackageType.cs b/StabilityMatrix.Core/Models/PackageType.cs
index 92a2a847e..33a9a51a3 100644
--- a/StabilityMatrix.Core/Models/PackageType.cs
+++ b/StabilityMatrix.Core/Models/PackageType.cs
@@ -3,5 +3,6 @@
public enum PackageType
{
SdInference,
- SdTraining
+ SdTraining,
+ Legacy,
}
diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs
index 224cb20ae..10d44e30c 100644
--- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs
@@ -22,8 +22,9 @@ public class A3WebUI(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -82,7 +83,7 @@ IPrerequisiteHelper prerequisiteHelper
[SharedOutputType.Text2Img] = ["outputs/txt2img-images"],
[SharedOutputType.Img2ImgGrids] = ["outputs/img2img-grids"],
[SharedOutputType.Text2ImgGrids] = ["outputs/txt2img-grids"],
- [SharedOutputType.SVD] = ["outputs/svd"]
+ [SharedOutputType.SVD] = ["outputs/svd"],
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
@@ -93,21 +94,21 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
- Options = ["--server-name"]
+ Options = ["--server-name"],
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
- Options = ["--port"]
+ Options = ["--port"],
},
new()
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
- Options = { "--share" }
+ Options = { "--share" },
},
new()
{
@@ -117,44 +118,44 @@ IPrerequisiteHelper prerequisiteHelper
{
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--medvram",
- _ => null
+ _ => null,
},
- Options = ["--lowvram", "--medvram", "--medvram-sdxl"]
+ Options = ["--lowvram", "--medvram", "--medvram-sdxl"],
},
new()
{
Name = "Xformers",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasNvidiaGpu(),
- Options = ["--xformers"]
+ Options = ["--xformers"],
},
new()
{
Name = "API",
Type = LaunchOptionType.Bool,
InitialValue = true,
- Options = ["--api"]
+ Options = ["--api"],
},
new()
{
Name = "Auto Launch Web UI",
Type = LaunchOptionType.Bool,
InitialValue = false,
- Options = ["--autolaunch"]
+ Options = ["--autolaunch"],
},
new()
{
Name = "Skip Torch CUDA Check",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
- Options = ["--skip-torch-cuda-test"]
+ Options = ["--skip-torch-cuda-test"],
},
new()
{
Name = "Skip Python Version Check",
Type = LaunchOptionType.Bool,
InitialValue = true,
- Options = ["--skip-python-version-check"]
+ Options = ["--skip-python-version-check"],
},
new()
{
@@ -163,22 +164,22 @@ IPrerequisiteHelper prerequisiteHelper
Description = "Do not switch the model to 16-bit floats",
InitialValue =
HardwareHelper.PreferRocm() || HardwareHelper.PreferDirectMLOrZluda() || Compat.IsMacOS,
- Options = ["--no-half"]
+ Options = ["--no-half"],
},
new()
{
Name = "Skip SD Model Download",
Type = LaunchOptionType.Bool,
InitialValue = false,
- Options = ["--no-download-sd-model"]
+ Options = ["--no-download-sd-model"],
},
new()
{
Name = "Skip Install",
Type = LaunchOptionType.Bool,
- Options = ["--skip-install"]
+ Options = ["--skip-install"],
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
];
public override IEnumerable AvailableSharedFolderMethods =>
@@ -203,66 +204,51 @@ public override async Task InstallPackage(
)
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
-
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
- await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
-
- progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
-
- var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
-
- var requirements = new FilePath(installLocation, "requirements_versions.txt");
- var pipArgs = torchVersion switch
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
+
+ var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
+ var isBlackwell =
+ torchIndex is TorchIndex.Cuda
+ && (SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu());
+
+ // 1. Configure the entire install process declaratively.
+ var config = new PipInstallConfig
{
- TorchIndex.Mps
- => new PipInstallArgs()
- .WithTorch("==2.3.1")
- .WithTorchVision("==0.18.1")
- .WithParsedFromRequirementsTxt(
- await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
- excludePattern: "torch"
- ),
- _
- => new PipInstallArgs()
- .WithTorch("==2.1.2")
- .WithTorchVision("==0.16.2")
- .WithTorchExtraIndex(
- torchVersion switch
- {
- TorchIndex.Cpu => "cpu",
- TorchIndex.Cuda => "cu121",
- TorchIndex.Rocm => "rocm5.6",
- TorchIndex.Mps => "cpu",
- _ => throw new NotSupportedException($"Unsupported torch version: {torchVersion}")
- }
- )
- .WithParsedFromRequirementsTxt(
- await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
- excludePattern: "torch"
- )
+ RequirementsFilePaths = ["requirements_versions.txt"],
+ TorchVersion = torchIndex == TorchIndex.Mps ? "==2.3.1" : (isBlackwell ? "" : "==2.1.2"),
+ TorchvisionVersion = torchIndex == TorchIndex.Mps ? "==0.18.1" : (isBlackwell ? "" : "==0.16.2"),
+ XformersVersion = isBlackwell ? " " : "==0.0.23.post1",
+ CudaIndex = isBlackwell ? "cu128" : "cu121",
+ RocmIndex = "rocm5.6",
+ ExtraPipArgs =
+ [
+ "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip",
+ ],
};
- if (torchVersion == TorchIndex.Cuda)
- {
- pipArgs = pipArgs.WithXFormers("==0.0.23.post1");
- }
-
- if (installedPackage.PipOverrides != null)
- {
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
- }
-
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
-
+ // 2. Execute the standardized installation process.
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+
+ // 3. Perform any final, package-specific tasks.
progress?.Report(new ProgressReport(-1f, "Updating configuration", isIndeterminate: true));
-
- // Create and add {"show_progress_type": "TAESD"} to config.json
- // Only add if the file doesn't exist
var configPath = Path.Combine(installLocation, "config.json");
if (!File.Exists(configPath))
{
- var config = new JsonObject { { "show_progress_type", "TAESD" } };
- await File.WriteAllTextAsync(configPath, config.ToString(), cancellationToken)
+ var configJson = new JsonObject { { "show_progress_type", "TAESD" } };
+ await File.WriteAllTextAsync(configPath, configJson.ToString(), cancellationToken)
.ConfigureAwait(false);
}
@@ -277,7 +263,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
void HandleConsoleOutput(ProcessOutput s)
{
@@ -298,8 +285,8 @@ void HandleConsoleOutput(ProcessOutput s)
VenvRunner.RunDetached(
[
Path.Combine(installLocation, options.Command ?? LaunchCommand),
- ..options.Arguments,
- ..ExtraLaunchArguments
+ .. options.Arguments,
+ .. ExtraLaunchArguments,
],
HandleConsoleOutput,
OnExit
@@ -320,7 +307,7 @@ private class A3WebUiExtensionManager(A3WebUI package)
new Uri(
"https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json"
)
- )
+ ),
];
public override async Task> GetManifestExtensionsAsync(
diff --git a/StabilityMatrix.Core/Models/Packages/AiToolkit.cs b/StabilityMatrix.Core/Models/Packages/AiToolkit.cs
new file mode 100644
index 000000000..7b2c13bb3
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Packages/AiToolkit.cs
@@ -0,0 +1,203 @@
+using System.Collections.Immutable;
+using System.Text.RegularExpressions;
+using Injectio.Attributes;
+using NLog;
+using StabilityMatrix.Core.Extensions;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Helper.Cache;
+using StabilityMatrix.Core.Helper.HardwareInfo;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
+using StabilityMatrix.Core.Services;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+
+namespace StabilityMatrix.Core.Models.Packages;
+
+[RegisterSingleton(Duplicate = DuplicateStrategy.Append)]
+public class AiToolkit(
+ IGithubApiCache githubApi,
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
+{
+ private AnsiProcess? npmProcess;
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ public override string Name => "ai-toolkit";
+ public override string DisplayName { get; set; } = "AI-Toolkit";
+ public override string Author => "ostris";
+ public override string Blurb => "AI Toolkit is an all in one training suite for diffusion models";
+ public override string LicenseType => "MIT";
+ public override string LicenseUrl => "https://github.com/ostris/ai-toolkit/blob/main/LICENSE";
+ public override string LaunchCommand => string.Empty;
+
+ public override Uri PreviewImageUri =>
+ new(
+ "https://camo.githubusercontent.com/ea35b399e0d659f9f2ee09cbedb58e1a3ec7a0eab763e8ae8d11d076aad5be40/68747470733a2f2f6f73747269732e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032352f30322f746f6f6c6b69742d75692e6a7067"
+ );
+
+ public override string OutputFolderName => "output";
+ public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda];
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;
+ public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;
+ public override List LaunchOptions => [];
+ public override Dictionary>? SharedOutputFolders => [];
+ public override string MainBranch => "main";
+ public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();
+
+ public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Cuda;
+
+ public override PackageType PackageType => PackageType.SdTraining;
+ public override bool OfferInOneClickInstaller => false;
+ public override bool ShouldIgnoreReleases => true;
+ public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13;
+
+ public override IEnumerable Prerequisites =>
+ base.Prerequisites.Concat([PackagePrerequisite.Node]);
+
+ public override async Task InstallPackage(
+ string installLocation,
+ InstalledPackage installedPackage,
+ InstallPackageOptions options,
+ IProgress? progress = null,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
+ venvRunner.UpdateEnvironmentVariables(GetEnvVars);
+
+ var isBlackwell =
+ SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu();
+
+ var config = new PipInstallConfig
+ {
+ RequirementsFilePaths = ["requirements.txt"],
+ TorchVersion = "==2.7.0",
+ TorchvisionVersion = "==0.22.0",
+ TorchaudioVersion = "==2.7.0",
+ CudaIndex = isBlackwell ? "cu128" : "cu126",
+ ExtraPipArgs = [Compat.IsWindows ? "triton-windows" : "triton"],
+ UpgradePackages = true,
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+
+ progress?.Report(new ProgressReport(-1f, "Installing AI Toolkit UI...", isIndeterminate: true));
+
+ var uiDirectory = new DirectoryPath(installLocation, "ui");
+ var envVars = GetEnvVars(venvRunner.EnvironmentVariables);
+ await PrerequisiteHelper
+ .RunNpm("install", uiDirectory, progress?.AsProcessOutputHandler(), envVars)
+ .ConfigureAwait(false);
+ await PrerequisiteHelper
+ .RunNpm("run update_db", uiDirectory, progress?.AsProcessOutputHandler(), envVars)
+ .ConfigureAwait(false);
+ await PrerequisiteHelper
+ .RunNpm("run build", uiDirectory, progress?.AsProcessOutputHandler(), envVars)
+ .ConfigureAwait(false);
+ }
+
+ public override async Task RunPackage(
+ string installLocation,
+ InstalledPackage installedPackage,
+ RunPackageOptions options,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
+ VenvRunner.UpdateEnvironmentVariables(GetEnvVars);
+
+ var uiDirectory = new DirectoryPath(installLocation, "ui");
+ var envVars = GetEnvVars(VenvRunner.EnvironmentVariables);
+ npmProcess = PrerequisiteHelper.RunNpmDetached(
+ "run start",
+ uiDirectory,
+ HandleConsoleOutput,
+ envVars
+ );
+ npmProcess.EnableRaisingEvents = true;
+ if (Compat.IsWindows)
+ {
+ ProcessTracker.AttachExitHandlerJobToProcess(npmProcess);
+ }
+
+ return;
+
+ void HandleConsoleOutput(ProcessOutput s)
+ {
+ onConsoleOutput?.Invoke(s);
+
+ if (!s.Text.Contains("Local: ", StringComparison.OrdinalIgnoreCase))
+ return;
+
+ var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)");
+ var match = regex.Match(s.Text);
+ if (match.Success)
+ {
+ WebUrl = match.Value;
+ }
+ OnStartupComplete(WebUrl);
+ }
+ }
+
+ public override async Task WaitForShutdown()
+ {
+ if (npmProcess is { HasExited: false })
+ {
+ npmProcess.Kill(true);
+ try
+ {
+ await npmProcess
+ .WaitForExitAsync(new CancellationTokenSource(5000).Token)
+ .ConfigureAwait(false);
+ }
+ catch (OperationCanceledException e)
+ {
+ Logger.Warn(e, "Timed out waiting for npm to exit");
+ npmProcess.CancelStreamReaders();
+ }
+ }
+ npmProcess = null;
+ }
+
+ private ImmutableDictionary GetEnvVars(ImmutableDictionary env)
+ {
+ var pathBuilder = new EnvPathBuilder();
+
+ if (env.TryGetValue("PATH", out var value))
+ {
+ pathBuilder.AddPath(value);
+ }
+
+ pathBuilder.AddPath(
+ Compat.IsWindows
+ ? Environment.GetFolderPath(Environment.SpecialFolder.System)
+ : Path.Combine(SettingsManager.LibraryDir, "Assets", "nodejs", "bin")
+ );
+
+ pathBuilder.AddPath(Path.Combine(SettingsManager.LibraryDir, "Assets", "nodejs"));
+
+ return env.SetItem("PATH", pathBuilder.ToString());
+ }
+}
diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
index 622df87e4..c52c9fb37 100644
--- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
+++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
@@ -28,7 +28,8 @@ public abstract class BaseGitPackage : BasePackage
protected readonly IGithubApiCache GithubApi;
protected readonly IDownloadService DownloadService;
protected readonly IPrerequisiteHelper PrerequisiteHelper;
- public PyVenvRunner? VenvRunner;
+ protected readonly IPyInstallationManager PyInstallationManager;
+ public IPyVenvRunner? VenvRunner;
public virtual string RepositoryName => Name;
public virtual string RepositoryAuthor => Author;
@@ -66,13 +67,15 @@ protected BaseGitPackage(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
)
: base(settingsManager)
{
GithubApi = githubApi;
DownloadService = downloadService;
PrerequisiteHelper = prerequisiteHelper;
+ PyInstallationManager = pyInstallationManager;
}
public override async Task GetLatestVersion(
@@ -89,7 +92,7 @@ IPrerequisiteHelper prerequisiteHelper
IsLatest = true,
IsPrerelease = false,
BranchName = MainBranch,
- CommitHash = commits?.FirstOrDefault()?.Sha
+ CommitHash = commits?.FirstOrDefault()?.Sha,
};
}
@@ -101,7 +104,7 @@ IPrerequisiteHelper prerequisiteHelper
{
IsLatest = true,
IsPrerelease = false,
- BranchName = MainBranch
+ BranchName = MainBranch,
};
}
@@ -111,7 +114,7 @@ IPrerequisiteHelper prerequisiteHelper
{
IsLatest = true,
IsPrerelease = latestRelease.Prerelease,
- VersionTag = latestRelease.TagName!
+ VersionTag = latestRelease.TagName!,
};
}
@@ -132,15 +135,12 @@ public override async Task GetAllVersionOptions()
var releasesList = allReleases.ToList();
if (releasesList.Any())
{
- packageVersionOptions.AvailableVersions = releasesList.Select(
- r =>
- new PackageVersion
- {
- TagName = r.TagName!,
- ReleaseNotesMarkdown = r.Body,
- IsPrerelease = r.Prerelease
- }
- );
+ packageVersionOptions.AvailableVersions = releasesList.Select(r => new PackageVersion
+ {
+ TagName = r.TagName!,
+ ReleaseNotesMarkdown = r.Body,
+ IsPrerelease = r.Prerelease,
+ });
}
}
@@ -148,9 +148,11 @@ public override async Task GetAllVersionOptions()
var allBranches = await GithubApi
.GetAllBranches(RepositoryAuthor, RepositoryName)
.ConfigureAwait(false);
- packageVersionOptions.AvailableBranches = allBranches.Select(
- b => new PackageVersion { TagName = $"{b.Name}", ReleaseNotesMarkdown = string.Empty }
- );
+ packageVersionOptions.AvailableBranches = allBranches.Select(b => new PackageVersion
+ {
+ TagName = $"{b.Name}",
+ ReleaseNotesMarkdown = string.Empty,
+ });
return packageVersionOptions;
}
@@ -159,11 +161,12 @@ public override async Task GetAllVersionOptions()
/// Setup the virtual environment for the package.
///
[MemberNotNull(nameof(VenvRunner))]
- public async Task SetupVenv(
+ public async Task SetupVenv(
string installedPackagePath,
string venvName = "venv",
bool forceRecreate = false,
- Action? onConsoleOutput = null
+ Action? onConsoleOutput = null,
+ PyVersion? pythonVersion = null
)
{
if (Interlocked.Exchange(ref VenvRunner, null) is { } oldRunner)
@@ -171,7 +174,13 @@ public async Task SetupVenv(
await oldRunner.DisposeAsync().ConfigureAwait(false);
}
- var venvRunner = await SetupVenvPure(installedPackagePath, venvName, forceRecreate, onConsoleOutput)
+ var venvRunner = await SetupVenvPure(
+ installedPackagePath,
+ venvName,
+ forceRecreate,
+ onConsoleOutput,
+ pythonVersion
+ )
.ConfigureAwait(false);
if (Interlocked.Exchange(ref VenvRunner, venvRunner) is { } oldRunner2)
@@ -188,15 +197,28 @@ public async Task SetupVenv(
/// Like , but does not set the property.
/// Returns a new instance.
///
- public async Task SetupVenvPure(
+ public async Task SetupVenvPure(
string installedPackagePath,
string venvName = "venv",
bool forceRecreate = false,
- Action? onConsoleOutput = null
+ Action? onConsoleOutput = null,
+ PyVersion? pythonVersion = null
)
{
- var venvRunner = await PyBaseInstall
- .Default.CreateVenvRunnerAsync(
+ // Use either the specific version or the default one
+ var baseInstall = pythonVersion.HasValue
+ ? new PyBaseInstall(
+ await PyInstallationManager.GetInstallationAsync(pythonVersion.Value).ConfigureAwait(false)
+ )
+ : PyBaseInstall.Default;
+
+ if (!PrerequisiteHelper.IsUvInstalled)
+ {
+ await PrerequisiteHelper.InstallUvIfNecessary().ConfigureAwait(false);
+ }
+
+ var venvRunner = await baseInstall
+ .CreateVenvRunnerAsync(
Path.Combine(installedPackagePath, venvName),
workingDirectory: installedPackagePath,
environmentVariables: SettingsManager.Settings.EnvironmentVariables,
@@ -210,12 +232,17 @@ public async Task SetupVenvPure(
await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false);
}
+ // ensure pip is installed
+ await venvRunner.PipInstall("pip", onConsoleOutput).ConfigureAwait(false);
+
if (!Compat.IsWindows)
return venvRunner;
try
{
- await PrerequisiteHelper.AddMissingLibsToVenv(installedPackagePath).ConfigureAwait(false);
+ await PrerequisiteHelper
+ .AddMissingLibsToVenv(installedPackagePath, baseInstall)
+ .ConfigureAwait(false);
}
catch (Exception e)
{
@@ -261,7 +288,7 @@ await PrerequisiteHelper
? versionOptions.VersionTag
: versionOptions.BranchName ?? MainBranch,
GithubUrl,
- installLocation
+ installLocation,
},
progress?.AsProcessOutputHandler()
)
@@ -516,7 +543,7 @@ await InstallPackage(
return new InstalledPackageVersion
{
InstalledReleaseVersion = versionOptions.VersionTag,
- IsPrerelease = versionOptions.IsPrerelease
+ IsPrerelease = versionOptions.IsPrerelease,
};
}
@@ -546,13 +573,31 @@ await PrerequisiteHelper
// pull
progress?.Report(new ProgressReport(-1f, "Pulling changes...", isIndeterminate: true));
- await PrerequisiteHelper
- .RunGit(
- new[] { "pull", "--autostash", "origin", installedPackage.Version.InstalledBranch! },
- onConsoleOutput,
+ // Try fast-forward-only first
+ var ffOnly = await PrerequisiteHelper
+ .GetGitOutput(
+ ["pull", "--ff-only", "--autostash", "origin", installedPackage.Version.InstalledBranch!],
installedPackage.FullPath!
)
.ConfigureAwait(false);
+
+ if (ffOnly.ExitCode != 0)
+ {
+ // Fallback to rebase to preserve local changes if any
+ var rebaseRes = await PrerequisiteHelper
+ .GetGitOutput(
+ [
+ "pull",
+ "--rebase",
+ "--autostash",
+ "origin",
+ installedPackage.Version.InstalledBranch!,
+ ],
+ installedPackage.FullPath!
+ )
+ .ConfigureAwait(false);
+ rebaseRes.EnsureSuccessExitCode();
+ }
}
else
{
@@ -587,7 +632,7 @@ await InstallPackage(
{
InstalledBranch = versionOptions.BranchName,
InstalledCommitSha = versionOptions.CommitHash,
- IsPrerelease = versionOptions.IsPrerelease
+ IsPrerelease = versionOptions.IsPrerelease,
};
}
@@ -703,7 +748,7 @@ await SharedFoldersConfigHelper
Path.Combine("ControlNet", "ControlNet"),
Path.Combine("IPAdapter", "base"),
Path.Combine("IPAdapter", "sd15"),
- Path.Combine("IPAdapter", "sdxl")
+ Path.Combine("IPAdapter", "sdxl"),
];
foreach (var duplicatePath in duplicatePaths)
@@ -799,6 +844,148 @@ public virtual async Task SendInputAsync(string input)
await process.StandardInput.WriteLineAsync(input).ConfigureAwait(false);
}
+ protected PipInstallArgs GetTorchPipArgs(
+ TorchIndex torchIndex,
+ string torchVersion = "",
+ string torchvisionVersion = "",
+ string torchaudioVersion = "",
+ string xformersVersion = "",
+ string cudaIndex = "cu128",
+ string rocmIndex = "rocm6.4"
+ )
+ {
+ var pipArgs = new PipInstallArgs();
+
+ if (torchIndex == TorchIndex.DirectMl)
+ {
+ return pipArgs.WithTorchDirectML();
+ }
+
+ pipArgs = pipArgs.WithTorch(torchVersion).WithTorchVision(torchvisionVersion);
+
+ if (!string.IsNullOrEmpty(torchaudioVersion))
+ {
+ pipArgs = pipArgs.WithTorchAudio(torchaudioVersion);
+ }
+
+ var extraIndex = torchIndex switch
+ {
+ TorchIndex.Cpu => "cpu",
+ TorchIndex.Cuda => cudaIndex,
+ TorchIndex.Rocm => rocmIndex,
+ TorchIndex.Mps => "cpu",
+ TorchIndex.Zluda => cudaIndex,
+ _ => "cpu",
+ };
+
+ pipArgs = pipArgs.WithTorchExtraIndex(extraIndex);
+
+ if (torchIndex is TorchIndex.Cuda or TorchIndex.Zluda && !string.IsNullOrEmpty(xformersVersion))
+ {
+ pipArgs = pipArgs.WithXFormers(xformersVersion);
+ }
+
+ return pipArgs;
+ }
+
+ ///
+ /// Executes a standardized pip installation workflow: requirements first, then a forced torch install.
+ ///
+ protected async Task StandardPipInstallProcessAsync(
+ IPyVenvRunner venvRunner,
+ InstallPackageOptions options,
+ InstalledPackage installedPackage,
+ PipInstallConfig config,
+ Action? onConsoleOutput = null,
+ IProgress? progress = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ progress?.Report(new ProgressReport(-1f, "Upgrading pip...", isIndeterminate: true));
+ await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
+
+ if (config.PrePipInstallArgs.Any())
+ {
+ await venvRunner
+ .PipInstall(new PipInstallArgs([.. config.PrePipInstallArgs]), onConsoleOutput)
+ .ConfigureAwait(false);
+ }
+
+ progress?.Report(
+ new ProgressReport(-1f, "Installing package requirements...", isIndeterminate: true)
+ );
+ var requirementsPipArgs = new PipInstallArgs([.. config.ExtraPipArgs]);
+
+ if (config.UpgradePackages)
+ {
+ requirementsPipArgs = requirementsPipArgs.AddArg("--upgrade");
+ }
+
+ foreach (var path in config.RequirementsFilePaths)
+ {
+ var requirementsFile = new FilePath(venvRunner.WorkingDirectory!, path);
+ if (!requirementsFile.Exists)
+ continue;
+
+ var content = await requirementsFile.ReadAllTextAsync(cancellationToken).ConfigureAwait(false);
+ requirementsPipArgs = requirementsPipArgs.WithParsedFromRequirementsTxt(
+ content,
+ config.RequirementsExcludePattern
+ );
+ }
+
+ if (installedPackage.PipOverrides != null)
+ {
+ requirementsPipArgs = requirementsPipArgs.WithUserOverrides(installedPackage.PipOverrides);
+ }
+
+ await venvRunner.PipInstall(requirementsPipArgs, onConsoleOutput).ConfigureAwait(false);
+
+ if (config.SkipTorchInstall)
+ return;
+
+ progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true));
+ var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
+
+ var torchPipArgs = GetTorchPipArgs(
+ torchIndex,
+ config.TorchVersion,
+ config.TorchvisionVersion,
+ config.TorchaudioVersion,
+ config.XformersVersion,
+ config.CudaIndex,
+ config.RocmIndex
+ );
+
+ if (config.UpgradePackages)
+ {
+ torchPipArgs = torchPipArgs.AddArg("--upgrade");
+ }
+
+ if (config.ForceReinstallTorch)
+ {
+ torchPipArgs = torchPipArgs.AddArg("--force-reinstall");
+ }
+
+ if (installedPackage.PipOverrides != null)
+ {
+ torchPipArgs = torchPipArgs.WithUserOverrides(installedPackage.PipOverrides);
+ }
+
+ await venvRunner.PipInstall(torchPipArgs, onConsoleOutput).ConfigureAwait(false);
+
+ if (config.PostInstallPipArgs.Any())
+ {
+ var postInstallPipArgs = new PipInstallArgs([.. config.PostInstallPipArgs]);
+ if (installedPackage.PipOverrides != null)
+ {
+ postInstallPipArgs = postInstallPipArgs.WithUserOverrides(installedPackage.PipOverrides);
+ }
+
+ await venvRunner.PipInstall(postInstallPipArgs, onConsoleOutput).ConfigureAwait(false);
+ }
+ }
+
///
public override void Shutdown()
{
diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs
index ac9e7722a..10779e8bb 100644
--- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs
+++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs
@@ -38,10 +38,12 @@ public abstract class BasePackage(ISettingsManager settingsManager)
///
/// Optional commands (e.g. 'config') that are on the launch button split drop-down.
///
- public virtual IReadOnlyList ExtraLaunchCommands { get; } = Array.Empty();
+ public virtual IReadOnlyDictionary ExtraLaunchCommands { get; } =
+ new Dictionary();
public abstract Uri PreviewImageUri { get; }
public virtual bool ShouldIgnoreReleases => false;
+ public virtual bool ShouldIgnoreBranches => false;
public virtual bool UpdateAvailable { get; set; }
public virtual bool IsInferenceCompatible => false;
@@ -58,6 +60,7 @@ public abstract class BasePackage(ISettingsManager settingsManager)
public virtual bool UsesVenv => true;
public virtual bool InstallRequiresAdmin => false;
public virtual string? AdminRequiredReason => null;
+ public virtual PyVersion RecommendedPythonVersion => PyInstallationManager.Python_3_10_17;
///
/// Returns a list of extra commands that can be executed for this package.
@@ -299,54 +302,9 @@ public virtual TorchIndex GetRecommendedTorchVersion()
PackagePrerequisite.Git,
PackagePrerequisite.Python310,
PackagePrerequisite.VcRedist,
- PackagePrerequisite.VcBuildTools
+ PackagePrerequisite.VcBuildTools,
];
- protected async Task InstallCudaTorch(
- PyVenvRunner venvRunner,
- IProgress? progress = null,
- Action? onConsoleOutput = null
- )
- {
- progress?.Report(new ProgressReport(-1f, "Installing PyTorch for CUDA", isIndeterminate: true));
-
- await venvRunner
- .PipInstall(
- new PipInstallArgs()
- .WithTorch("==2.1.2")
- .WithTorchVision("==0.16.2")
- .WithXFormers("==0.0.23post1")
- .WithTorchExtraIndex("cu121"),
- onConsoleOutput
- )
- .ConfigureAwait(false);
- }
-
- protected Task InstallDirectMlTorch(
- PyVenvRunner venvRunner,
- IProgress? progress = null,
- Action? onConsoleOutput = null
- )
- {
- progress?.Report(new ProgressReport(-1f, "Installing PyTorch for DirectML", isIndeterminate: true));
-
- return venvRunner.PipInstall(new PipInstallArgs().WithTorchDirectML(), onConsoleOutput);
- }
-
- protected Task InstallCpuTorch(
- PyVenvRunner venvRunner,
- IProgress? progress = null,
- Action? onConsoleOutput = null
- )
- {
- progress?.Report(new ProgressReport(-1f, "Installing PyTorch for CPU", isIndeterminate: true));
-
- return venvRunner.PipInstall(
- new PipInstallArgs().WithTorch("==2.1.2").WithTorchVision(),
- onConsoleOutput
- );
- }
-
public abstract Task GetUpdate(InstalledPackage installedPackage);
///
diff --git a/StabilityMatrix.Core/Models/Packages/Cogstudio.cs b/StabilityMatrix.Core/Models/Packages/Cogstudio.cs
index 078d2ec69..4f088ff16 100644
--- a/StabilityMatrix.Core/Models/Packages/Cogstudio.cs
+++ b/StabilityMatrix.Core/Models/Packages/Cogstudio.cs
@@ -15,8 +15,9 @@ public class Cogstudio(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "Cogstudio";
public override string DisplayName { get; set; } = "Cogstudio";
@@ -56,7 +57,11 @@ public override async Task InstallPackage(
"https://raw.githubusercontent.com/pinokiofactory/cogstudio/refs/heads/main/cogstudio.py";
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Setting up Cogstudio files", isIndeterminate: true));
var gradioCompositeDemo = new FilePath(installLocation, "inference/gradio_composite_demo");
@@ -130,7 +135,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
void HandleConsoleOutput(ProcessOutput s)
{
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
index 51aed8602..dbce07e0e 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs
@@ -15,10 +15,6 @@
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
-using YamlDotNet.Core;
-using YamlDotNet.RepresentationModel;
-using YamlDotNet.Serialization;
-using YamlDotNet.Serialization.NamingConventions;
namespace StabilityMatrix.Core.Models.Packages;
@@ -27,8 +23,9 @@ public class ComfyUI(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public override string Name => "ComfyUI";
@@ -40,12 +37,12 @@ IPrerequisiteHelper prerequisiteHelper
public override string LaunchCommand => "main.py";
public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/comfyui/preview.webp");
- public override bool ShouldIgnoreReleases => true;
public override bool IsInferenceCompatible => true;
public override string OutputFolderName => "output";
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.InferenceCompatible;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;
+ public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_12_10;
// https://github.com/comfyanonymous/ComfyUI/blob/master/folder_paths.py#L11
public override SharedFolderLayout SharedFolderLayout =>
@@ -56,7 +53,7 @@ IPrerequisiteHelper prerequisiteHelper
ConfigSharingOptions =
{
RootKey = "stability_matrix",
- ConfigDefaultType = ConfigDefaultType.ClearRoot
+ ConfigDefaultType = ConfigDefaultType.ClearRoot,
},
Rules =
[
@@ -64,61 +61,61 @@ IPrerequisiteHelper prerequisiteHelper
{
SourceTypes = [SharedFolderType.StableDiffusion],
TargetRelativePaths = ["models/checkpoints"],
- ConfigDocumentPaths = ["checkpoints"]
+ ConfigDocumentPaths = ["checkpoints"],
},
new SharedFolderLayoutRule // Diffusers
{
SourceTypes = [SharedFolderType.Diffusers],
TargetRelativePaths = ["models/diffusers"],
- ConfigDocumentPaths = ["diffusers"]
+ ConfigDocumentPaths = ["diffusers"],
},
new SharedFolderLayoutRule // Loras
{
SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],
TargetRelativePaths = ["models/loras"],
- ConfigDocumentPaths = ["loras"]
+ ConfigDocumentPaths = ["loras"],
},
new SharedFolderLayoutRule // CLIP (Text Encoders)
{
SourceTypes = [SharedFolderType.TextEncoders],
TargetRelativePaths = ["models/clip"],
- ConfigDocumentPaths = ["clip"]
+ ConfigDocumentPaths = ["clip"],
},
new SharedFolderLayoutRule // CLIP Vision
{
SourceTypes = [SharedFolderType.ClipVision],
TargetRelativePaths = ["models/clip_vision"],
- ConfigDocumentPaths = ["clip_vision"]
+ ConfigDocumentPaths = ["clip_vision"],
},
new SharedFolderLayoutRule // Embeddings / Textual Inversion
{
SourceTypes = [SharedFolderType.Embeddings],
TargetRelativePaths = ["models/embeddings"],
- ConfigDocumentPaths = ["embeddings"]
+ ConfigDocumentPaths = ["embeddings"],
},
new SharedFolderLayoutRule // VAE
{
SourceTypes = [SharedFolderType.VAE],
TargetRelativePaths = ["models/vae"],
- ConfigDocumentPaths = ["vae"]
+ ConfigDocumentPaths = ["vae"],
},
new SharedFolderLayoutRule // VAE Approx
{
SourceTypes = [SharedFolderType.ApproxVAE],
TargetRelativePaths = ["models/vae_approx"],
- ConfigDocumentPaths = ["vae_approx"]
+ ConfigDocumentPaths = ["vae_approx"],
},
new SharedFolderLayoutRule // ControlNet / T2IAdapter
{
SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter],
TargetRelativePaths = ["models/controlnet"],
- ConfigDocumentPaths = ["controlnet"]
+ ConfigDocumentPaths = ["controlnet"],
},
new SharedFolderLayoutRule // GLIGEN
{
SourceTypes = [SharedFolderType.GLIGEN],
TargetRelativePaths = ["models/gligen"],
- ConfigDocumentPaths = ["gligen"]
+ ConfigDocumentPaths = ["gligen"],
},
new SharedFolderLayoutRule // Upscalers
{
@@ -126,16 +123,16 @@ IPrerequisiteHelper prerequisiteHelper
[
SharedFolderType.ESRGAN,
SharedFolderType.RealESRGAN,
- SharedFolderType.SwinIR
+ SharedFolderType.SwinIR,
],
TargetRelativePaths = ["models/upscale_models"],
- ConfigDocumentPaths = ["upscale_models"]
+ ConfigDocumentPaths = ["upscale_models"],
},
new SharedFolderLayoutRule // Hypernetworks
{
SourceTypes = [SharedFolderType.Hypernetwork],
TargetRelativePaths = ["models/hypernetworks"],
- ConfigDocumentPaths = ["hypernetworks"]
+ ConfigDocumentPaths = ["hypernetworks"],
},
new SharedFolderLayoutRule // IP-Adapter Base, SD1.5, SDXL
{
@@ -143,49 +140,49 @@ IPrerequisiteHelper prerequisiteHelper
[
SharedFolderType.IpAdapter,
SharedFolderType.IpAdapters15,
- SharedFolderType.IpAdaptersXl
+ SharedFolderType.IpAdaptersXl,
],
TargetRelativePaths = ["models/ipadapter"], // Single target path
- ConfigDocumentPaths = ["ipadapter"]
+ ConfigDocumentPaths = ["ipadapter"],
},
new SharedFolderLayoutRule // Prompt Expansion
{
SourceTypes = [SharedFolderType.PromptExpansion],
TargetRelativePaths = ["models/prompt_expansion"],
- ConfigDocumentPaths = ["prompt_expansion"]
+ ConfigDocumentPaths = ["prompt_expansion"],
},
new SharedFolderLayoutRule // Ultralytics
{
SourceTypes = [SharedFolderType.Ultralytics], // Might need specific UltralyticsBbox/Segm if symlinks differ
TargetRelativePaths = ["models/ultralytics"],
- ConfigDocumentPaths = ["ultralytics"]
+ ConfigDocumentPaths = ["ultralytics"],
},
// Config only rules for Ultralytics bbox/segm
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Ultralytics],
SourceSubPath = "bbox",
- ConfigDocumentPaths = ["ultralytics_bbox"]
+ ConfigDocumentPaths = ["ultralytics_bbox"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Ultralytics],
SourceSubPath = "segm",
- ConfigDocumentPaths = ["ultralytics_segm"]
+ ConfigDocumentPaths = ["ultralytics_segm"],
},
new SharedFolderLayoutRule // SAMs
{
SourceTypes = [SharedFolderType.Sams],
TargetRelativePaths = ["models/sams"],
- ConfigDocumentPaths = ["sams"]
+ ConfigDocumentPaths = ["sams"],
},
new SharedFolderLayoutRule // Diffusion Models / Unet
{
SourceTypes = [SharedFolderType.DiffusionModels],
TargetRelativePaths = ["models/diffusion_models"],
- ConfigDocumentPaths = ["diffusion_models"]
+ ConfigDocumentPaths = ["diffusion_models"],
},
- ]
+ ],
};
public override Dictionary>? SharedOutputFolders =>
@@ -198,14 +195,14 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "127.0.0.1",
- Options = ["--listen"]
+ Options = ["--listen"],
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "8188",
- Options = ["--port"]
+ Options = ["--port"],
},
new()
{
@@ -215,9 +212,9 @@ IPrerequisiteHelper prerequisiteHelper
{
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--normalvram",
- _ => null
+ _ => null,
},
- Options = ["--highvram", "--normalvram", "--lowvram", "--novram"]
+ Options = ["--highvram", "--normalvram", "--lowvram", "--novram"],
},
new()
{
@@ -226,21 +223,24 @@ IPrerequisiteHelper prerequisiteHelper
InitialValue = Compat.IsWindows && HardwareHelper.HasAmdGpu() ? "0.9" : null,
Description =
"Sets the amount of VRAM (in GB) you want to reserve for use by your OS/other software",
- Options = ["--reserve-vram"]
+ Options = ["--reserve-vram"],
},
new()
{
Name = "Preview Method",
Type = LaunchOptionType.Bool,
InitialValue = "--preview-method auto",
- Options = ["--preview-method auto", "--preview-method latent2rgb", "--preview-method taesd"]
+ Options = ["--preview-method auto", "--preview-method latent2rgb", "--preview-method taesd"],
},
new()
{
Name = "Enable DirectML",
Type = LaunchOptionType.Bool,
- InitialValue = HardwareHelper.PreferDirectMLOrZluda() && this is not ComfyZluda,
- Options = ["--directml"]
+ InitialValue =
+ !HardwareHelper.HasWindowsRocmSupportedGpu()
+ && HardwareHelper.PreferDirectMLOrZluda()
+ && this is not ComfyZluda,
+ Options = ["--directml"],
},
new()
{
@@ -248,58 +248,54 @@ IPrerequisiteHelper prerequisiteHelper
Type = LaunchOptionType.Bool,
InitialValue =
!Compat.IsMacOS && !HardwareHelper.HasNvidiaGpu() && !HardwareHelper.HasAmdGpu(),
- Options = ["--cpu"]
+ Options = ["--cpu"],
},
new()
{
Name = "Cross Attention Method",
Type = LaunchOptionType.Bool,
- InitialValue = Compat.IsMacOS
- ? "--use-pytorch-cross-attention"
- : (Compat.IsWindows && HardwareHelper.HasAmdGpu())
- ? "--use-quad-cross-attention"
- : null,
+ InitialValue = "--use-pytorch-cross-attention",
Options =
[
"--use-split-cross-attention",
"--use-quad-cross-attention",
"--use-pytorch-cross-attention",
- "--use-sage-attention"
- ]
+ "--use-sage-attention",
+ ],
},
new()
{
Name = "Force Floating Point Precision",
Type = LaunchOptionType.Bool,
InitialValue = Compat.IsMacOS ? "--force-fp16" : null,
- Options = ["--force-fp32", "--force-fp16"]
+ Options = ["--force-fp32", "--force-fp16"],
},
new()
{
Name = "VAE Precision",
Type = LaunchOptionType.Bool,
- Options = ["--fp16-vae", "--fp32-vae", "--bf16-vae"]
+ Options = ["--fp16-vae", "--fp32-vae", "--bf16-vae"],
},
new()
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
- Options = ["--disable-xformers"]
+ Options = ["--disable-xformers"],
},
new()
{
Name = "Disable upcasting of attention",
Type = LaunchOptionType.Bool,
- Options = ["--dont-upcast-attention"]
+ Options = ["--dont-upcast-attention"],
},
new()
{
Name = "Auto-Launch",
Type = LaunchOptionType.Bool,
- Options = ["--auto-launch"]
+ Options = ["--auto-launch"],
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
];
public override string MainBranch => "master";
@@ -307,27 +303,30 @@ IPrerequisiteHelper prerequisiteHelper
public override IEnumerable AvailableTorchIndices =>
[TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps];
- public override List GetExtraCommands() =>
- Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true
- ?
- [
+ public override List GetExtraCommands()
+ {
+ var commands = new List();
+
+ if (Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true)
+ {
+ commands.Add(
new ExtraPackageCommand
{
CommandName = "Install Triton and SageAttention",
- Command = async installedPackage =>
- {
- if (installedPackage == null || string.IsNullOrEmpty(installedPackage.FullPath))
- {
- throw new InvalidOperationException(
- "Package not found or not installed correctly"
- );
- }
-
- await InstallTritonAndSageAttention(installedPackage).ConfigureAwait(false);
- }
+ Command = InstallTritonAndSageAttention,
}
- ]
- : [];
+ );
+ }
+
+ if (!Compat.IsMacOS && SettingsManager.Settings.PreferredGpu?.ComputeCapabilityValue is >= 7.5m)
+ {
+ commands.Add(
+ new ExtraPackageCommand { CommandName = "Install Nunchaku", Command = InstallNunchaku }
+ );
+ }
+
+ return commands;
+ }
public override async Task InstallPackage(
string installLocation,
@@ -339,63 +338,97 @@ public override async Task InstallPackage(
)
{
progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
-
- await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
+
+ var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
+ var gfxArch =
+ SettingsManager.Settings.PreferredGpu?.GetAmdGfxArch()
+ ?? HardwareHelper.GetWindowsRocmSupportedGpu()?.GetAmdGfxArch();
+
+ // Special case for Windows ROCm Nightly builds
+ if (
+ Compat.IsWindows
+ && !string.IsNullOrWhiteSpace(gfxArch)
+ && torchIndex is TorchIndex.Rocm
+ && options.PythonOptions.PythonVersion >= PyVersion.Parse("3.11.0")
+ )
+ {
+ var config = new PipInstallConfig
+ {
+ RequirementsFilePaths = ["requirements.txt"],
+ ExtraPipArgs = ["numpy<2"],
+ SkipTorchInstall = true,
+ };
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
- var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
- var isLegacyNvidia =
- torchVersion == TorchIndex.Cuda
- && (
- SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu()
- ?? HardwareHelper.HasLegacyNvidiaGpu()
+ progress?.Report(
+ new ProgressReport(-1f, "Installing ROCm nightly torch...", isIndeterminate: true)
);
+ var indexUrl = gfxArch switch
+ {
+ "gfx1151" => "https://rocm.nightlies.amd.com/v2/gfx1151",
+ _ when gfxArch.StartsWith("gfx110") => "https://rocm.nightlies.amd.com/v2/gfx110X-dgpu",
+ _ when gfxArch.StartsWith("gfx120") => "https://rocm.nightlies.amd.com/v2/gfx120X-all",
+ _ => throw new ArgumentOutOfRangeException(
+ nameof(gfxArch),
+ $"Unsupported GFX Arch: {gfxArch}"
+ ),
+ };
- var pipArgs = new PipInstallArgs();
-
- pipArgs = torchVersion switch
- {
- TorchIndex.DirectMl => pipArgs.WithTorchDirectML(),
- _ => pipArgs
- .AddArg("--upgrade")
+ var torchPipArgs = new PipInstallArgs()
+ .AddArgs("--pre", "--upgrade")
.WithTorch()
.WithTorchVision()
.WithTorchAudio()
- .WithTorchExtraIndex(
- torchVersion switch
- {
- TorchIndex.Cpu => "cpu",
- TorchIndex.Cuda when isLegacyNvidia => "cu126",
- TorchIndex.Cuda => "cu128",
- TorchIndex.Rocm => "rocm6.2.4",
- TorchIndex.Mps => "cpu",
- _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null),
- }
- ),
- };
-
- var requirements = new FilePath(installLocation, "requirements.txt");
+ .AddArgs("--index-url", indexUrl);
- pipArgs = pipArgs.WithParsedFromRequirementsTxt(
- await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
- excludePattern: "torch$|numpy"
- );
-
- // https://github.com/comfyanonymous/ComfyUI/pull/4121
- // https://github.com/comfyanonymous/ComfyUI/commit/e6829e7ac5bef5db8099005b5b038c49e173e87c
- pipArgs = pipArgs.AddArg("numpy<2");
-
- if (installedPackage.PipOverrides != null)
- {
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
+ await venvRunner.PipInstall(torchPipArgs, onConsoleOutput).ConfigureAwait(false);
}
+ else // Standard installation path for all other cases
+ {
+ var isLegacyNvidia =
+ torchIndex == TorchIndex.Cuda
+ && (
+ SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu()
+ ?? HardwareHelper.HasLegacyNvidiaGpu()
+ );
- progress?.Report(
- new ProgressReport(-1f, "Installing Package Requirements...", isIndeterminate: true)
- );
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
+ var config = new PipInstallConfig
+ {
+ RequirementsFilePaths = ["requirements.txt"],
+ ExtraPipArgs = ["numpy<2"],
+ TorchaudioVersion = " ", // Request torchaudio without a specific version
+ CudaIndex = isLegacyNvidia ? "cu126" : "cu129",
+ RocmIndex = "rocm6.4",
+ UpgradePackages = true,
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ }
- progress?.Report(new ProgressReport(1, "Installed Package Requirements", isIndeterminate: false));
+ progress?.Report(new ProgressReport(1, "Install complete", isIndeterminate: false));
}
public override async Task RunPackage(
@@ -406,19 +439,16 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ // Use the same Python version that was used for installation
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
VenvRunner.RunDetached(
- [Path.Combine(installLocation, options.Command ?? LaunchCommand), ..options.Arguments],
+ [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],
HandleConsoleOutput,
OnExit
);
- if (Compat.IsWindows)
- {
- ProcessTracker.AttachExitHandlerJobToProcess(VenvRunner.Process);
- }
-
return;
void HandleConsoleOutput(ProcessOutput s)
@@ -438,6 +468,26 @@ void HandleConsoleOutput(ProcessOutput s)
}
}
+ public override TorchIndex GetRecommendedTorchVersion()
+ {
+ var preferRocm =
+ (Compat.IsLinux && (SettingsManager.Settings.PreferredGpu?.IsAmd ?? HardwareHelper.PreferRocm()))
+ || (
+ Compat.IsWindows
+ && (
+ SettingsManager.Settings.PreferredGpu?.IsWindowsRocmSupportedGpu()
+ ?? HardwareHelper.HasWindowsRocmSupportedGpu()
+ )
+ );
+
+ if (AvailableTorchIndices.Contains(TorchIndex.Rocm) && preferRocm)
+ {
+ return TorchIndex.Rocm;
+ }
+
+ return base.GetRecommendedTorchVersion();
+ }
+
public override IPackageExtensionManager ExtensionManager =>
new ComfyExtensionManager(this, settingsManager);
@@ -449,7 +499,7 @@ private class ComfyExtensionManager(ComfyUI package, ISettingsManager settingsMa
public override IEnumerable DefaultManifests =>
[
"https://cdn.jsdelivr.net/gh/ltdrdata/ComfyUI-Manager/custom-node-list.json",
- "https://cdn.jsdelivr.net/gh/LykosAI/ComfyUI-Extensions-Index/custom-node-list.json"
+ "https://cdn.jsdelivr.net/gh/LykosAI/ComfyUI-Extensions-Index/custom-node-list.json",
];
public override async Task> GetManifestExtensionsAsync(
@@ -493,12 +543,12 @@ public override async Task UpdateExtensionAsync(
)
{
await base.UpdateExtensionAsync(
- installedExtension,
- installedPackage,
- version,
- progress,
- cancellationToken
- )
+ installedExtension,
+ installedPackage,
+ version,
+ progress,
+ cancellationToken
+ )
.ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@@ -525,12 +575,12 @@ public override async Task InstallExtensionAsync(
)
{
await base.InstallExtensionAsync(
- extension,
- installedPackage,
- version,
- progress,
- cancellationToken
- )
+ extension,
+ installedPackage,
+ version,
+ progress,
+ cancellationToken
+ )
.ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@@ -562,7 +612,10 @@ private async Task PostInstallAsync(
if (extension.Pip != null)
{
await using var venvRunner = await package
- .SetupVenvPure(installedPackage.FullPath!)
+ .SetupVenvPure(
+ installedPackage.FullPath!,
+ pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)
+ )
.ConfigureAwait(false);
var pipArgs = new PipInstallArgs();
@@ -595,7 +648,10 @@ await venvRunner
);
await using var venvRunner = await package
- .SetupVenvPure(installedPackage.FullPath!)
+ .SetupVenvPure(
+ installedPackage.FullPath!,
+ pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)
+ )
.ConfigureAwait(false);
var pipArgs = new PipInstallArgs().WithParsedFromRequirementsTxt(requirementsContent);
@@ -624,7 +680,10 @@ await venvRunner
);
await using var venvRunner = await package
- .SetupVenvPure(installedPackage.FullPath!)
+ .SetupVenvPure(
+ installedPackage.FullPath!,
+ pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)
+ )
.ConfigureAwait(false);
venvRunner.WorkingDirectory = installScript.Directory;
@@ -660,18 +719,22 @@ await venvRunner
}
}
- private async Task InstallTritonAndSageAttention(InstalledPackage installedPackage)
+ private async Task InstallTritonAndSageAttention(InstalledPackage? installedPackage)
{
if (installedPackage?.FullPath is null)
return;
- var installSageStep = new InstallSageAttentionStep(DownloadService, PrerequisiteHelper)
+ var installSageStep = new InstallSageAttentionStep(
+ DownloadService,
+ PrerequisiteHelper,
+ PyInstallationManager
+ )
{
InstalledPackage = installedPackage,
WorkingDirectory = new DirectoryPath(installedPackage.FullPath),
EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables,
IsBlackwellGpu =
- SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu()
+ SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu(),
};
var runner = new PackageModificationRunner
@@ -715,9 +778,34 @@ private async Task InstallTritonAndSageAttention(InstalledPackage installedPacka
{
Name = "--use-sage-attention",
Type = LaunchOptionType.Bool,
- OptionValue = true
+ OptionValue = true,
}
);
}
}
+
+ private async Task InstallNunchaku(InstalledPackage? installedPackage)
+ {
+ if (installedPackage?.FullPath is null)
+ return;
+
+ var installNunchaku = new InstallNunchakuStep(PyInstallationManager)
+ {
+ InstalledPackage = installedPackage,
+ WorkingDirectory = new DirectoryPath(installedPackage.FullPath),
+ EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables,
+ PreferredGpu =
+ SettingsManager.Settings.PreferredGpu
+ ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia || x.IsAmd),
+ ComfyExtensionManager = ExtensionManager,
+ };
+
+ var runner = new PackageModificationRunner
+ {
+ ShowDialogOnStart = true,
+ ModificationCompleteMessage = "Nunchaku installed successfully",
+ };
+ EventManager.Instance.OnPackageInstallProgressAdded(runner);
+ await runner.ExecuteSteps([installNunchaku]).ConfigureAwait(false);
+ }
}
diff --git a/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs b/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs
index 6a864f8b8..2ff3f4ef1 100644
--- a/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs
+++ b/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs
@@ -19,11 +19,15 @@ public class ComfyZluda(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : ComfyUI(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : ComfyUI(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private const string ZludaPatchDownloadUrl =
- "https://github.com/lshqqytiger/ZLUDA/releases/download/rel.dba64c0966df2c71e82255e942c96e2e1cea3a2d/ZLUDA-windows-rocm6-amd64.zip";
+ "https://github.com/lshqqytiger/ZLUDA/releases/download/rel.5e717459179dc272b7d7d23391f0fad66c7459cf/ZLUDA-nightly-windows-rocm6-amd64.zip";
+
+ private const string HipSdkExtensionDownloadUrl = "https://cdn.lykos.ai/HIP-SDK-extension.7z";
+
private Process? zludaProcess;
public override string Name => "ComfyUI-Zluda";
@@ -40,8 +44,12 @@ IPrerequisiteHelper prerequisiteHelper
public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Zluda;
+ public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13;
+
public override bool IsCompatible => HardwareHelper.PreferDirectMLOrZluda();
+ public override bool ShouldIgnoreReleases => true;
+
public override IEnumerable Prerequisites =>
base.Prerequisites.Concat([PackagePrerequisite.HipSdk]);
@@ -61,36 +69,69 @@ public override async Task InstallPackage(
if (!PrerequisiteHelper.IsHipSdkInstalled) // for updates
{
progress?.Report(new ProgressReport(-1, "Installing HIP SDK 6.2", isIndeterminate: true));
- await PrerequisiteHelper.InstallPackageRequirements(this, progress).ConfigureAwait(false);
+ await PrerequisiteHelper
+ .InstallPackageRequirements(this, options.PythonOptions.PythonVersion, progress)
+ .ConfigureAwait(false);
}
- progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
- await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
-
- var pipArgs = new PipInstallArgs()
- .WithTorch("==2.3.0")
- .WithTorchVision("==0.18.0")
- .WithTorchAudio("==2.3.0")
- .WithTorchExtraIndex("cu118");
-
- var requirements = new FilePath(installLocation, "requirements.txt");
- pipArgs = pipArgs.WithParsedFromRequirementsTxt(
- await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
- excludePattern: "torch$|numpy"
+ // download & setup hip sdk extension if not already done
+ var hipPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
+ "AMD",
+ "ROCm",
+ "6.2"
);
+ var hipblasltPath = new DirectoryPath(hipPath, "hipblaslt");
+ var otherHipPath = new DirectoryPath(hipPath, "include", "hipblaslt");
- pipArgs = pipArgs.AddArg("numpy==1.26.0");
-
- if (installedPackage.PipOverrides != null)
+ if (!hipblasltPath.Exists || !otherHipPath.Exists)
{
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
+ var hipSdkExtensionPath = new FilePath(
+ SettingsManager.LibraryDir,
+ "Assets",
+ "hip-sdk-extension.7z"
+ );
+ await DownloadService
+ .DownloadToFileAsync(
+ HipSdkExtensionDownloadUrl,
+ hipSdkExtensionPath,
+ progress,
+ cancellationToken: cancellationToken
+ )
+ .ConfigureAwait(false);
+
+ await ArchiveHelper.Extract7Z(hipSdkExtensionPath, hipPath, progress).ConfigureAwait(false);
}
- progress?.Report(
- new ProgressReport(-1f, "Installing Package Requirements...", isIndeterminate: true)
- );
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
+ progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
+
+ var config = new PipInstallConfig
+ {
+ RequirementsFilePaths = ["requirements.txt"],
+ RequirementsExcludePattern = "(torch|numpy)", // Keep numpy excluded for specific install below
+ TorchVersion = "==2.7.0",
+ TorchvisionVersion = "==0.22.0",
+ TorchaudioVersion = "==2.7.0",
+ CudaIndex = "cu118",
+ ForceReinstallTorch = true,
+ ExtraPipArgs = ["numpy==1.26.0"],
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
progress?.Report(new ProgressReport(1, "Installed Package Requirements", isIndeterminate: false));
@@ -98,6 +139,13 @@ await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
// patch zluda
var zludaPatchPath = new FilePath(installLocation, "zluda.zip");
+ var zludaExtractPath = new DirectoryPath(installLocation, "zluda");
+ if (zludaExtractPath.Exists)
+ {
+ await zludaExtractPath.DeleteAsync(true).ConfigureAwait(false);
+ }
+ zludaExtractPath.Create();
+
await downloadService
.DownloadToFileAsync(
ZludaPatchDownloadUrl,
@@ -107,12 +155,26 @@ await downloadService
)
.ConfigureAwait(false);
- await ArchiveHelper.Extract(zludaPatchPath, installLocation, progress).ConfigureAwait(false);
+ await ArchiveHelper.Extract(zludaPatchPath, zludaExtractPath, progress).ConfigureAwait(false);
+ await zludaPatchPath.DeleteAsync(cancellationToken).ConfigureAwait(false);
// copy some stuff into the venv
var cublasSourcePath = new FilePath(installLocation, "zluda", "cublas.dll");
var cusparseSourcePath = new FilePath(installLocation, "zluda", "cusparse.dll");
+ var nvrtc112SourcePath = new FilePath(
+ venvRunner.RootPath,
+ "Lib",
+ "site-packages",
+ "torch",
+ "lib",
+ "nvrtc64_112_0.dll"
+ );
var nvrtcSourcePath = new FilePath(installLocation, "zluda", "nvrtc.dll");
+ var cudnnSourcePath = new FilePath(installLocation, "zluda", "cudnn.dll");
+ var cufftSourcePath = new FilePath(installLocation, "zluda", "cufft.dll");
+ var cufftwSourcePath = new FilePath(installLocation, "zluda", "cufftw.dll");
+ var zludaPySourcePath = new FilePath(installLocation, "comfy", "customzluda", "zluda.py");
+
var cublasDestPath = new FilePath(
venvRunner.RootPath,
"Lib",
@@ -129,6 +191,14 @@ await downloadService
"lib",
"cusparse64_11.dll"
);
+ var nvrtc112DestPath = new FilePath(
+ venvRunner.RootPath,
+ "Lib",
+ "site-packages",
+ "torch",
+ "lib",
+ "nvrtc_cuda.dll"
+ );
var nvrtcDestPath = new FilePath(
venvRunner.RootPath,
"Lib",
@@ -137,10 +207,40 @@ await downloadService
"lib",
"nvrtc64_112_0.dll"
);
+ var cudnnDestPath = new FilePath(
+ venvRunner.RootPath,
+ "Lib",
+ "site-packages",
+ "torch",
+ "lib",
+ "cudnn64_9.dll"
+ );
+ var cufftDestPath = new FilePath(
+ venvRunner.RootPath,
+ "Lib",
+ "site-packages",
+ "torch",
+ "lib",
+ "cufft64_10.dll"
+ );
+ var cufftwDestPath = new FilePath(
+ venvRunner.RootPath,
+ "Lib",
+ "site-packages",
+ "torch",
+ "lib",
+ "cufftw64_10.dll"
+ );
+ var zludaPyDestPath = new FilePath(installLocation, "comfy", "zluda.py");
await cublasSourcePath.CopyToAsync(cublasDestPath, true).ConfigureAwait(false);
await cusparseSourcePath.CopyToAsync(cusparseDestPath, true).ConfigureAwait(false);
+ await nvrtc112SourcePath.CopyToAsync(nvrtc112DestPath, true).ConfigureAwait(false);
await nvrtcSourcePath.CopyToAsync(nvrtcDestPath, true).ConfigureAwait(false);
+ await cudnnSourcePath.CopyToAsync(cudnnDestPath, true).ConfigureAwait(false);
+ await cufftSourcePath.CopyToAsync(cufftDestPath, true).ConfigureAwait(false);
+ await cufftwSourcePath.CopyToAsync(cufftwDestPath, true).ConfigureAwait(false);
+ await zludaPySourcePath.CopyToAsync(zludaPyDestPath, true).ConfigureAwait(false);
progress?.Report(new ProgressReport(1, "Installed ZLUDA", isIndeterminate: false));
}
@@ -160,8 +260,8 @@ public override async Task RunPackage(
"Your package has not yet been upgraded to use HIP SDK 6.2. To continue, please update this package or select \"Change Version\" from the 3-dots menu to have it upgraded automatically for you"
);
}
-
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
var portableGitBin = new DirectoryPath(PrerequisiteHelper.GitBinPath);
var hipPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
@@ -175,7 +275,7 @@ public override async Task RunPackage(
["ZLUDA_COMGR_LOG_LEVEL"] = "1",
["HIP_PATH"] = hipPath,
["HIP_PATH_62"] = hipPath,
- ["GIT"] = portableGitBin.JoinFile("git.exe")
+ ["GIT"] = portableGitBin.JoinFile("git.exe"),
};
envVars.Update(settingsManager.Settings.EnvironmentVariables);
@@ -189,7 +289,7 @@ public override async Task RunPackage(
}
var zludaPath = Path.Combine(installLocation, LaunchCommand);
- ProcessArgs args = ["--", VenvRunner.PythonPath.ToString(), "main.py", ..options.Arguments];
+ ProcessArgs args = ["--", VenvRunner.PythonPath.ToString(), "main.py", .. options.Arguments];
zludaProcess = ProcessRunner.StartAnsiProcess(
zludaPath,
args,
diff --git a/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs b/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs
index f4ee35fea..ccc44c349 100644
--- a/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs
+++ b/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs
@@ -3,6 +3,7 @@
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
@@ -13,9 +14,10 @@ public DankDiffusion(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
)
- : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
+ : base(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) { }
public override string Name => "dank-diffusion";
public override string DisplayName { get; set; } = "Dank Diffusion";
@@ -26,7 +28,8 @@ IPrerequisiteHelper prerequisiteHelper
public override string LaunchCommand => "test";
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
- public override IReadOnlyList ExtraLaunchCommands => new[] { "test-config", };
+ public override IReadOnlyDictionary ExtraLaunchCommands =>
+ new Dictionary { ["test-config"] = "test-config" };
public override Uri PreviewImageUri { get; }
diff --git a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs
index cfbb1f400..3cfce64bb 100644
--- a/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs
+++ b/StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs
@@ -2,6 +2,7 @@
using KGySoft.CoreLibraries;
using Microsoft.Extensions.Caching.Memory;
using NLog;
+using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.FileInterfaces;
@@ -80,9 +81,10 @@ public virtual async Task> GetInstalledEx
// Search for installed extensions in the package's index directories.
foreach (
- var indexDirectory in IndexRelativeDirectories.Select(
- path => new DirectoryPath(packagePath, path)
- )
+ var indexDirectory in IndexRelativeDirectories.Select(path => new DirectoryPath(
+ packagePath,
+ path
+ ))
)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -120,11 +122,11 @@ var indexDirectory in IndexRelativeDirectories.Select(
{
Tag = version.Tag,
Branch = version.Branch,
- CommitSha = version.CommitSha
+ CommitSha = version.CommitSha,
},
GitRepositoryUrl = remoteUrlResult.IsSuccessExitCode
? remoteUrlResult.StandardOutput?.Trim()
- : null
+ : null,
}
);
}
@@ -150,9 +152,10 @@ public virtual async Task> GetInstalledEx
// Search for installed extensions in the package's index directories.
foreach (
- var indexDirectory in IndexRelativeDirectories.Select(
- path => new DirectoryPath(packagePath, path)
- )
+ var indexDirectory in IndexRelativeDirectories.Select(path => new DirectoryPath(
+ packagePath,
+ path
+ ))
)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -221,8 +224,8 @@ InstalledPackageExtension installedExtension
{
Tag = version.Tag,
Branch = version.Branch,
- CommitSha = version.CommitSha
- }
+ CommitSha = version.CommitSha,
+ },
};
}
@@ -257,14 +260,34 @@ public virtual async Task InstallExtensionAsync(
new ProgressReport(0f, message: $"Cloning {repositoryUri}", isIndeterminate: true)
);
- await prerequisiteHelper
- .CloneGitRepository(
- cloneRoot,
- repositoryUri.ToString(),
- version,
- progress.AsProcessOutputHandler()
- )
- .ConfigureAwait(false);
+ try
+ {
+ await prerequisiteHelper
+ .CloneGitRepository(
+ cloneRoot,
+ repositoryUri.ToString(),
+ version,
+ progress.AsProcessOutputHandler()
+ )
+ .ConfigureAwait(false);
+ }
+ catch (ProcessException ex)
+ {
+ if (ex.Message.Contains("Git exited with code 128"))
+ {
+ progress?.Report(
+ new ProgressReport(
+ -1f,
+ $"Unable to check out commit {version?.CommitSha} - continuing with latest commit from {version?.Branch}\n\n{ex.ProcessResult?.StandardError}\n",
+ isIndeterminate: true
+ )
+ );
+ }
+ else
+ {
+ throw;
+ }
+ }
progress?.Report(new ProgressReport(1f, message: $"Cloned {repositoryUri}"));
}
diff --git a/StabilityMatrix.Core/Models/Packages/FluxGym.cs b/StabilityMatrix.Core/Models/Packages/FluxGym.cs
index dc63a4513..dfb9d0748 100644
--- a/StabilityMatrix.Core/Models/Packages/FluxGym.cs
+++ b/StabilityMatrix.Core/Models/Packages/FluxGym.cs
@@ -16,8 +16,9 @@ public class FluxGym(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "FluxGym";
public override string DisplayName { get; set; } = "FluxGym";
@@ -100,43 +101,38 @@ await PrerequisiteHelper
}
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
-
- progress?.Report(
- new ProgressReport(-1f, "Installing sd-scripts requirements", isIndeterminate: true)
- );
- var sdsRequirements = new FilePath(installLocation, "sd-scripts", "requirements.txt");
- var sdsPipArgs = new PipInstallArgs()
- .WithParsedFromRequirementsTxt(
- await sdsRequirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
- "torch"
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
)
- .RemovePipArgKey("-e .");
- await venvRunner.PipInstall(sdsPipArgs, onConsoleOutput).ConfigureAwait(false);
-
- progress?.Report(new ProgressReport(-1f, "Installing requirements", isIndeterminate: true));
+ .ConfigureAwait(false);
var isLegacyNvidiaGpu =
SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();
- var requirements = new FilePath(installLocation, "requirements.txt");
- var pipArgs = new PipInstallArgs()
- .WithTorch()
- .WithTorchVision()
- .WithTorchAudio()
- .WithTorchExtraIndex(isLegacyNvidiaGpu ? "cu126" : "cu128")
- .AddArg("bitsandbytes>=0.46.0")
- .WithParsedFromRequirementsTxt(
- await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
- "torch$|bitsandbytes"
- );
-
- if (installedPackage.PipOverrides != null)
+ var config = new PipInstallConfig
{
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
- }
+ RequirementsFilePaths = ["sd-scripts/requirements.txt", "requirements.txt"],
+ RequirementsExcludePattern =
+ "(diffusers\\[torch\\]==0.32.1|torch|torchvision|torchaudio|xformers|bitsandbytes|-e\\s\\.)",
+ TorchaudioVersion = " ",
+ CudaIndex = isLegacyNvidiaGpu ? "cu126" : "cu128",
+ ExtraPipArgs = ["bitsandbytes>=0.46.0"],
+ PostInstallPipArgs = ["diffusers[torch]==0.32.1"],
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
+ await venvRunner.PipInstall(["-e", "./sd-scripts"], onConsoleOutput).ConfigureAwait(false);
}
public override async Task RunPackage(
@@ -147,7 +143,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
void HandleConsoleOutput(ProcessOutput s)
{
diff --git a/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs b/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs
index 10187f6be..8b3a79d5d 100644
--- a/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs
+++ b/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs
@@ -1,6 +1,7 @@
using Injectio.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
+using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
@@ -10,24 +11,26 @@ public class FocusControlNet(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "Fooocus-ControlNet-SDXL";
public override string DisplayName { get; set; } = "Fooocus-ControlNet";
public override string Author => "fenneishi";
public override string Blurb => "Fooocus-ControlNet adds more control to the original Fooocus software.";
- public override string Disclaimer => "This package may no longer be actively maintained";
+ public override string Disclaimer => "This package may no longer receive updates from its author.";
public override string LicenseUrl =>
"https://github.com/fenneishi/Fooocus-ControlNet-SDXL/blob/main/LICENSE";
public override Uri PreviewImageUri =>
new("https://github.com/fenneishi/Fooocus-ControlNet-SDXL/raw/main/asset/canny/snip.png");
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;
public override bool OfferInOneClickInstaller => false;
+ public override PackageType PackageType => PackageType.Legacy;
public override SharedFolderLayout SharedFolderLayout =>
base.SharedFolderLayout with
{
- RelativeConfigPath = "user_path_config.txt"
+ RelativeConfigPath = "user_path_config.txt",
};
}
diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs
index 4e207bb1a..b2c0a6029 100644
--- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs
+++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs
@@ -17,8 +17,9 @@ public class Fooocus(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "Fooocus";
public override string DisplayName { get; set; } = "Fooocus";
@@ -41,66 +42,66 @@ IPrerequisiteHelper prerequisiteHelper
{
Name = "Preset",
Type = LaunchOptionType.Bool,
- Options = { "--preset anime", "--preset realistic" }
+ Options = { "--preset anime", "--preset realistic" },
},
new LaunchOptionDefinition
{
Name = "Port",
Type = LaunchOptionType.String,
Description = "Sets the listen port",
- Options = { "--port" }
+ Options = { "--port" },
},
new LaunchOptionDefinition
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
- Options = { "--share" }
+ Options = { "--share" },
},
new LaunchOptionDefinition
{
Name = "Listen",
Type = LaunchOptionType.String,
Description = "Set the listen interface",
- Options = { "--listen" }
+ Options = { "--listen" },
},
new LaunchOptionDefinition
{
Name = "Output Directory",
Type = LaunchOptionType.String,
Description = "Override the output directory",
- Options = { "--output-path" }
+ Options = { "--output-path" },
},
new LaunchOptionDefinition
{
Name = "Language",
Type = LaunchOptionType.String,
Description = "Change the language of the UI",
- Options = { "--language" }
+ Options = { "--language" },
},
new LaunchOptionDefinition
{
Name = "Disable Image Log",
Type = LaunchOptionType.Bool,
- Options = { "--disable-image-log" }
+ Options = { "--disable-image-log" },
},
new LaunchOptionDefinition
{
Name = "Disable Analytics",
Type = LaunchOptionType.Bool,
- Options = { "--disable-analytics" }
+ Options = { "--disable-analytics" },
},
new LaunchOptionDefinition
{
Name = "Disable Preset Model Downloads",
Type = LaunchOptionType.Bool,
- Options = { "--disable-preset-download" }
+ Options = { "--disable-preset-download" },
},
new LaunchOptionDefinition
{
Name = "Always Download Newer Models",
Type = LaunchOptionType.Bool,
- Options = { "--always-download-new-model" }
+ Options = { "--always-download-new-model" },
},
new()
{
@@ -110,15 +111,15 @@ IPrerequisiteHelper prerequisiteHelper
{
MemoryLevel.Low => "--always-low-vram",
MemoryLevel.Medium => "--always-normal-vram",
- _ => null
+ _ => null,
},
Options =
{
"--always-high-vram",
"--always-normal-vram",
"--always-low-vram",
- "--always-no-vram"
- }
+ "--always-no-vram",
+ },
},
new LaunchOptionDefinition
{
@@ -126,23 +127,23 @@ IPrerequisiteHelper prerequisiteHelper
Type = LaunchOptionType.Bool,
Description = "Use pytorch with DirectML support",
InitialValue = HardwareHelper.PreferDirectMLOrZluda(),
- Options = { "--directml" }
+ Options = { "--directml" },
},
new LaunchOptionDefinition
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
- Options = { "--disable-xformers" }
+ Options = { "--disable-xformers" },
},
new LaunchOptionDefinition
{
Name = "Disable Offload from VRAM",
Type = LaunchOptionType.Bool,
InitialValue = Compat.IsMacOS,
- Options = { "--disable-offload-from-vram" }
+ Options = { "--disable-offload-from-vram" },
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
};
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;
@@ -161,85 +162,85 @@ IPrerequisiteHelper prerequisiteHelper
{
SourceTypes = [SharedFolderType.StableDiffusion],
TargetRelativePaths = ["models/checkpoints"],
- ConfigDocumentPaths = ["path_checkpoints"]
+ ConfigDocumentPaths = ["path_checkpoints"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Diffusers],
- TargetRelativePaths = ["models/diffusers"]
+ TargetRelativePaths = ["models/diffusers"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.TextEncoders],
- TargetRelativePaths = ["models/clip"]
+ TargetRelativePaths = ["models/clip"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.GLIGEN],
- TargetRelativePaths = ["models/gligen"]
+ TargetRelativePaths = ["models/gligen"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ESRGAN],
- TargetRelativePaths = ["models/upscale_models"]
+ TargetRelativePaths = ["models/upscale_models"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Hypernetwork],
- TargetRelativePaths = ["models/hypernetworks"]
+ TargetRelativePaths = ["models/hypernetworks"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Embeddings],
TargetRelativePaths = ["models/embeddings"],
- ConfigDocumentPaths = ["path_embeddings"]
+ ConfigDocumentPaths = ["path_embeddings"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.VAE],
TargetRelativePaths = ["models/vae"],
- ConfigDocumentPaths = ["path_vae"]
+ ConfigDocumentPaths = ["path_vae"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ApproxVAE],
TargetRelativePaths = ["models/vae_approx"],
- ConfigDocumentPaths = ["path_vae_approx"]
+ ConfigDocumentPaths = ["path_vae_approx"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],
TargetRelativePaths = ["models/loras"],
- ConfigDocumentPaths = ["path_loras"]
+ ConfigDocumentPaths = ["path_loras"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ClipVision],
TargetRelativePaths = ["models/clip_vision"],
- ConfigDocumentPaths = ["path_clip_vision"]
+ ConfigDocumentPaths = ["path_clip_vision"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ControlNet],
TargetRelativePaths = ["models/controlnet"],
- ConfigDocumentPaths = ["path_controlnet"]
+ ConfigDocumentPaths = ["path_controlnet"],
},
new SharedFolderLayoutRule
{
TargetRelativePaths = ["models/inpaint"],
- ConfigDocumentPaths = ["path_inpaint"]
+ ConfigDocumentPaths = ["path_inpaint"],
},
new SharedFolderLayoutRule
{
TargetRelativePaths = ["models/prompt_expansion/fooocus_expansion"],
- ConfigDocumentPaths = ["path_fooocus_expansion"]
+ ConfigDocumentPaths = ["path_fooocus_expansion"],
},
new SharedFolderLayoutRule
{
TargetRelativePaths = [OutputFolderName],
- ConfigDocumentPaths = ["path_outputs"]
- }
- ]
+ ConfigDocumentPaths = ["path_outputs"],
+ },
+ ],
};
public override Dictionary> SharedOutputFolders =>
@@ -265,54 +266,38 @@ public override async Task InstallPackage(
CancellationToken cancellationToken = default
)
{
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
-
- progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
-
- // Pip version 24.1 deprecated numpy requirement spec used by torchsde 0.2.5
- await venvRunner.PipInstall(["pip==23.3.2"], onConsoleOutput).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
+ var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
var isBlackwell =
- SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu();
- var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
-
- var pipArgs = new PipInstallArgs();
+ torchIndex is TorchIndex.Cuda
+ && (SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu());
- if (torchVersion == TorchIndex.DirectMl)
- {
- pipArgs = pipArgs.WithTorchDirectML();
- }
- else
+ var config = new PipInstallConfig
{
- pipArgs = pipArgs
- .WithTorch(isBlackwell ? string.Empty : "==2.1.0")
- .WithTorchVision(isBlackwell ? string.Empty : "==0.16.0")
- .WithTorchExtraIndex(
- torchVersion switch
- {
- TorchIndex.Cpu => "cpu",
- TorchIndex.Cuda when isBlackwell => "cu128",
- TorchIndex.Cuda => "cu121",
- TorchIndex.Rocm => "rocm5.6",
- TorchIndex.Mps => "cpu",
- _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null)
- }
- );
- }
-
- var requirements = new FilePath(installLocation, "requirements_versions.txt");
-
- pipArgs = pipArgs.WithParsedFromRequirementsTxt(
- await requirements.ReadAllTextAsync().ConfigureAwait(false),
- excludePattern: "torch"
- );
-
- if (installedPackage.PipOverrides != null)
- {
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
- }
+ // Pip version 24.1 deprecated numpy requirement spec used by torchsde 0.2.5
+ PrePipInstallArgs = ["pip==23.3.2"],
+ RequirementsFilePaths = ["requirements_versions.txt"],
+ TorchVersion = isBlackwell ? "" : "==2.1.0",
+ TorchvisionVersion = isBlackwell ? "" : "==0.16.0",
+ CudaIndex = isBlackwell ? "cu128" : "cu121",
+ RocmIndex = "rocm5.6",
+ };
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
}
public override async Task RunPackage(
@@ -323,7 +308,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
void HandleConsoleOutput(ProcessOutput s)
{
@@ -342,7 +328,7 @@ void HandleConsoleOutput(ProcessOutput s)
}
VenvRunner.RunDetached(
- [Path.Combine(installLocation, options.Command ?? LaunchCommand), ..options.Arguments],
+ [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],
HandleConsoleOutput,
OnExit
);
diff --git a/StabilityMatrix.Core/Models/Packages/FooocusMre.cs b/StabilityMatrix.Core/Models/Packages/FooocusMre.cs
index 274c57e69..a0b264b4f 100644
--- a/StabilityMatrix.Core/Models/Packages/FooocusMre.cs
+++ b/StabilityMatrix.Core/Models/Packages/FooocusMre.cs
@@ -16,8 +16,9 @@ public class FooocusMre(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "Fooocus-MRE";
public override string DisplayName { get; set; } = "Fooocus-MRE";
@@ -37,11 +38,11 @@ IPrerequisiteHelper prerequisiteHelper
"https://user-images.githubusercontent.com/130458190/265366059-ce430ea0-0995-4067-98dd-cef1d7dc1ab6.png"
);
- public override string Disclaimer =>
- "This package may no longer receive updates from its author. It may be removed from Stability Matrix in the future.";
+ public override string Disclaimer => "This package may no longer receive updates from its author.";
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;
public override bool OfferInOneClickInstaller => false;
+ public override PackageType PackageType => PackageType.Legacy;
public override List LaunchOptions =>
new()
@@ -51,23 +52,23 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Port",
Type = LaunchOptionType.String,
Description = "Sets the listen port",
- Options = { "--port" }
+ Options = { "--port" },
},
new LaunchOptionDefinition
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
- Options = { "--share" }
+ Options = { "--share" },
},
new LaunchOptionDefinition
{
Name = "Listen",
Type = LaunchOptionType.String,
Description = "Set the listen interface",
- Options = { "--listen" }
+ Options = { "--listen" },
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
};
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
@@ -88,7 +89,7 @@ IPrerequisiteHelper prerequisiteHelper
[SharedFolderType.ControlNet] = new[] { "models/controlnet" },
[SharedFolderType.GLIGEN] = new[] { "models/gligen" },
[SharedFolderType.ESRGAN] = new[] { "models/upscale_models" },
- [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }
+ [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" },
};
public override Dictionary>? SharedOutputFolders =>
@@ -110,7 +111,11 @@ public override async Task InstallPackage(
CancellationToken cancellationToken = default
)
{
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true));
@@ -128,7 +133,7 @@ public override async Task InstallPackage(
TorchIndex.Cpu => "cpu",
TorchIndex.Cuda => "cu118",
TorchIndex.Rocm => "rocm5.4.2",
- _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null)
+ _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null),
};
pipInstallArgs = pipInstallArgs
@@ -159,7 +164,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
void HandleConsoleOutput(ProcessOutput s)
{
@@ -178,7 +184,7 @@ void HandleConsoleOutput(ProcessOutput s)
}
VenvRunner.RunDetached(
- [Path.Combine(installLocation, options.Command ?? LaunchCommand), ..options.Arguments],
+ [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],
HandleConsoleOutput,
OnExit
);
diff --git a/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs b/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs
index 17205af1f..b90bc63aa 100644
--- a/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs
+++ b/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs
@@ -9,6 +9,7 @@
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
@@ -18,8 +19,9 @@ public class ForgeAmdGpu(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "stable-diffusion-webui-amdgpu-forge";
public override string DisplayName => "Stable Diffusion WebUI AMDGPU Forge";
@@ -44,18 +46,19 @@ IPrerequisiteHelper prerequisiteHelper
base.Prerequisites.Concat([PackagePrerequisite.HipSdk]);
public override List LaunchOptions =>
- base.LaunchOptions.Concat(
- [
- new LaunchOptionDefinition
- {
- Name = "Use ZLUDA",
- Description = "Use ZLUDA for CUDA acceleration on AMD GPUs",
- Type = LaunchOptionType.Bool,
- InitialValue = HardwareHelper.PreferDirectMLOrZluda(),
- Options = ["--use-zluda"]
- }
- ]
- )
+ base
+ .LaunchOptions.Concat(
+ [
+ new LaunchOptionDefinition
+ {
+ Name = "Use ZLUDA",
+ Description = "Use ZLUDA for CUDA acceleration on AMD GPUs",
+ Type = LaunchOptionType.Bool,
+ InitialValue = HardwareHelper.PreferDirectMLOrZluda(),
+ Options = ["--use-zluda"],
+ },
+ ]
+ )
.ToList();
public override bool InstallRequiresAdmin => true;
@@ -76,11 +79,17 @@ public override async Task InstallPackage(
if (!PrerequisiteHelper.IsHipSdkInstalled) // for updates
{
progress?.Report(new ProgressReport(-1, "Installing HIP SDK 6.2", isIndeterminate: true));
- await PrerequisiteHelper.InstallPackageRequirements(this, progress).ConfigureAwait(false);
+ await PrerequisiteHelper
+ .InstallPackageRequirements(this, options.PythonOptions.PythonVersion, progress)
+ .ConfigureAwait(false);
}
progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(1, "Install finished", isIndeterminate: false));
}
@@ -100,8 +109,8 @@ public override async Task RunPackage(
"Your package has not yet been upgraded to use HIP SDK 6.2. To continue, please update this package or select \"Change Version\" from the 3-dots menu to have it upgraded automatically for you"
);
}
-
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
var portableGitBin = new DirectoryPath(PrerequisiteHelper.GitBinPath);
var hipPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
@@ -115,7 +124,7 @@ public override async Task RunPackage(
["ZLUDA_COMGR_LOG_LEVEL"] = "1",
["HIP_PATH"] = hipPath,
["HIP_PATH_62"] = hipPath,
- ["GIT"] = portableGitBin.JoinFile("git.exe")
+ ["GIT"] = portableGitBin.JoinFile("git.exe"),
};
envVars.Update(settingsManager.Settings.EnvironmentVariables);
@@ -133,8 +142,8 @@ public override async Task RunPackage(
VenvRunner.RunDetached(
[
Path.Combine(installLocation, options.Command ?? LaunchCommand),
- ..options.Arguments,
- ..ExtraLaunchArguments
+ .. options.Arguments,
+ .. ExtraLaunchArguments,
],
HandleConsoleOutput,
OnExit
diff --git a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs
index a06b44c10..ec628b789 100644
--- a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs
+++ b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs
@@ -1,4 +1,5 @@
-using Injectio.Attributes;
+using System.Text;
+using Injectio.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Helper.HardwareInfo;
@@ -15,8 +16,9 @@ public class ForgeClassic(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "forge-classic";
public override string Author => "Haoming02";
@@ -33,6 +35,8 @@ IPrerequisiteHelper prerequisiteHelper
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Recommended;
public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda];
public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();
+ public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13;
+
public override List LaunchOptions =>
[
new()
@@ -40,63 +44,63 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
- Options = ["--server-name"]
+ Options = ["--server-name"],
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
- Options = ["--port"]
+ Options = ["--port"],
},
new()
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
- Options = { "--share" }
+ Options = { "--share" },
},
new()
{
Name = "Xformers",
Type = LaunchOptionType.Bool,
Description = "Set whether to use xformers",
- Options = { "--xformers" }
+ Options = { "--xformers" },
},
new()
{
Name = "Use SageAttention",
Type = LaunchOptionType.Bool,
Description = "Set whether to use sage attention",
- Options = { "--sage" }
+ Options = { "--sage" },
},
new()
{
Name = "Pin Shared Memory",
Type = LaunchOptionType.Bool,
Options = { "--pin-shared-memory" },
- InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false
+ InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false,
},
new()
{
Name = "CUDA Malloc",
Type = LaunchOptionType.Bool,
Options = { "--cuda-malloc" },
- InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false
+ InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false,
},
new()
{
Name = "CUDA Stream",
Type = LaunchOptionType.Bool,
Options = { "--cuda-stream" },
- InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false
+ InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false,
},
new()
{
Name = "Auto Launch",
Type = LaunchOptionType.Bool,
Description = "Set whether to auto launch the webui",
- Options = { "--auto-launch" }
+ Options = { "--auto-launch" },
},
new()
{
@@ -104,7 +108,7 @@ IPrerequisiteHelper prerequisiteHelper
Type = LaunchOptionType.Bool,
Description = "Set whether to skip python version check",
Options = { "--skip-python-version-check" },
- InitialValue = true
+ InitialValue = true,
},
LaunchOptionDefinition.Extras,
];
@@ -141,37 +145,50 @@ public override async Task InstallPackage(
)
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
-
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
-
- await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
-
- progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
-
- var requirements = new FilePath(installLocation, "requirements.txt");
- var requirementsContent = await requirements
- .ReadAllTextAsync(cancellationToken)
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
.ConfigureAwait(false);
+ // Dynamically discover all requirements files
+ var requirementsPaths = new List { "requirements.txt" };
+ var extensionsBuiltinDir = new DirectoryPath(installLocation, "extensions-builtin");
+ if (extensionsBuiltinDir.Exists)
+ {
+ requirementsPaths.AddRange(
+ extensionsBuiltinDir
+ .EnumerateFiles("requirements.txt", EnumerationOptionConstants.AllDirectories)
+ .Select(f => Path.GetRelativePath(installLocation, f.ToString()))
+ );
+ }
+
var isLegacyNvidia =
SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();
- var torchExtraIndex = isLegacyNvidia ? "cu126" : "cu128";
-
- var pipArgs = new PipInstallArgs()
- .AddArg("--upgrade")
- .WithTorch()
- .WithTorchVision()
- .WithTorchAudio()
- .WithTorchExtraIndex(torchExtraIndex);
- pipArgs = pipArgs.WithParsedFromRequirementsTxt(requirementsContent, excludePattern: "torch");
-
- if (installedPackage.PipOverrides != null)
+ var config = new PipInstallConfig
{
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
- }
+ RequirementsFilePaths = requirementsPaths,
+ TorchaudioVersion = " ", // Request torchaudio installation
+ CudaIndex = isLegacyNvidia ? "cu126" : "cu128",
+ UpgradePackages = true,
+ ExtraPipArgs =
+ [
+ "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip",
+ ],
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false));
}
}
diff --git a/StabilityMatrix.Core/Models/Packages/FramePack.cs b/StabilityMatrix.Core/Models/Packages/FramePack.cs
new file mode 100644
index 000000000..b362f0281
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Packages/FramePack.cs
@@ -0,0 +1,266 @@
+using System.Text.RegularExpressions;
+using Injectio.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Helper.Cache;
+using StabilityMatrix.Core.Helper.HardwareInfo;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Models.PackageModification;
+using StabilityMatrix.Core.Models.Progress;
+using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Core.Models.Packages;
+
+[RegisterSingleton(Duplicate = DuplicateStrategy.Append)]
+public class FramePack(
+ IGithubApiCache githubApi,
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
+{
+ public override string Name => "framepack";
+ public override string DisplayName { get; set; } = "FramePack";
+ public override string Author => "lllyasviel";
+
+ public override string Blurb =>
+ "FramePack is a next-frame (next-frame-section) prediction neural network structure that generates videos progressively.";
+
+ public override string LicenseType => "Apache-2.0";
+ public override string LicenseUrl => "https://github.com/lllyasviel/FramePack/blob/main/LICENSE";
+ public override string LaunchCommand => "demo_gradio.py";
+ public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/framepack/framepack.png");
+ public override string OutputFolderName => "outputs";
+ public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda];
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;
+ public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;
+
+ public override List LaunchOptions =>
+ [
+ new()
+ {
+ Name = "Server",
+ Type = LaunchOptionType.String,
+ DefaultValue = "0.0.0.0",
+ InitialValue = "127.0.0.1",
+ Options = ["--server"],
+ },
+ new()
+ {
+ Name = "Port",
+ Type = LaunchOptionType.String,
+ DefaultValue = "7860",
+ Options = ["--port"],
+ },
+ new()
+ {
+ Name = "Share",
+ Type = LaunchOptionType.Bool,
+ Description = "Set whether to share on Gradio",
+ Options = ["--share"],
+ },
+ new()
+ {
+ Name = "In Browser",
+ Type = LaunchOptionType.Bool,
+ Options = ["--inbrowser"],
+ InitialValue = true,
+ },
+ ];
+
+ public override Dictionary>? SharedOutputFolders =>
+ new() { [SharedOutputType.Img2Vid] = ["outputs"] };
+
+ public override string MainBranch => "main";
+ public override bool ShouldIgnoreReleases => true;
+ public override IEnumerable AvailableSharedFolderMethods => [SharedFolderMethod.None];
+ public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();
+ public override IReadOnlyList ExtraLaunchArguments =>
+ settingsManager.IsLibraryDirSet ? ["--gradio-allowed-paths", settingsManager.ImagesDirectory] : [];
+
+ public override IReadOnlyDictionary ExtraLaunchCommands =>
+ new Dictionary { ["FramePack F1"] = "demo_gradio_f1.py" };
+
+ public override async Task InstallPackage(
+ string installLocation,
+ InstalledPackage installedPackage,
+ InstallPackageOptions options,
+ IProgress? progress = null,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
+
+ var isLegacyNvidia =
+ SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();
+ var isNewerNvidia =
+ SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu()
+ ?? HardwareHelper.HasAmpereOrNewerGpu();
+
+ var extraArgs = new List();
+ if (isNewerNvidia)
+ {
+ extraArgs.Add("triton-windows");
+ }
+
+ var config = new PipInstallConfig
+ {
+ RequirementsFilePaths = ["requirements.txt"],
+ TorchaudioVersion = " ", // Request torchaudio install
+ XformersVersion = " ", // Request xformers install
+ CudaIndex = isLegacyNvidia ? "cu126" : "cu128",
+ UpgradePackages = true,
+ ExtraPipArgs = extraArgs,
+ PostInstallPipArgs = ["numpy==1.26.4"],
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+
+ progress?.Report(new ProgressReport(1, "Install complete", isIndeterminate: false));
+ }
+
+ public override async Task RunPackage(
+ string installLocation,
+ InstalledPackage installedPackage,
+ RunPackageOptions options,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
+
+ // Path to the original demo_gradio.py file
+ var originalDemoPath = Path.Combine(installLocation, options.Command ?? LaunchCommand);
+ var modifiedDemoPath = Path.Combine(installLocation, "demo_gradio_modified.py");
+
+ // Read the original demo_gradio.py file
+ var originalContent = await File.ReadAllTextAsync(originalDemoPath, cancellationToken)
+ .ConfigureAwait(false);
+
+ // Modify the content to add --gradio-allowed-paths support
+ var modifiedContent = AddGradioAllowedPathsSupport(originalContent);
+
+ // Write the modified content to a new file
+ await File.WriteAllTextAsync(modifiedDemoPath, modifiedContent, cancellationToken)
+ .ConfigureAwait(false);
+
+ VenvRunner.RunDetached(
+ [modifiedDemoPath, .. options.Arguments, .. ExtraLaunchArguments],
+ HandleConsoleOutput,
+ OnExit
+ );
+
+ return;
+
+ void HandleConsoleOutput(ProcessOutput s)
+ {
+ onConsoleOutput?.Invoke(s);
+
+ if (!s.Text.Contains("Running on local URL", StringComparison.OrdinalIgnoreCase))
+ return;
+
+ var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)");
+ var match = regex.Match(s.Text);
+ if (match.Success)
+ WebUrl = match.Value;
+ OnStartupComplete(WebUrl);
+ }
+ }
+
+ public override List GetExtraCommands()
+ {
+ return Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true
+ ?
+ [
+ new ExtraPackageCommand
+ {
+ CommandName = "Install Triton and SageAttention",
+ Command = async installedPackage =>
+ {
+ if (installedPackage == null || string.IsNullOrEmpty(installedPackage.FullPath))
+ throw new InvalidOperationException(
+ "Package not found or not installed correctly"
+ );
+
+ await InstallTritonAndSageAttention(installedPackage).ConfigureAwait(false);
+ },
+ },
+ ]
+ : [];
+ }
+
+ private static string AddGradioAllowedPathsSupport(string originalContent)
+ {
+ // Add the --gradio-allowed-paths argument to the argument parser
+ var parserPattern =
+ @"(parser\.add_argument\(""--inbrowser"", action='store_true'\)\s*\n)(args = parser\.parse_args\(\))";
+ var parserReplacement =
+ "$1parser.add_argument('--gradio-allowed-paths', nargs='*', default=[], help='Allowed paths for Gradio file access')\n$2";
+
+ var modifiedContent = Regex.Replace(
+ originalContent,
+ parserPattern,
+ parserReplacement,
+ RegexOptions.Multiline
+ );
+
+ // Add the allowed_paths parameter to the block.launch() call
+ var launchPattern =
+ @"(block\.launch\(\s*\n\s*server_name=args\.server,\s*\n\s*server_port=args\.port,\s*\n\s*share=args\.share,\s*\n\s*inbrowser=args\.inbrowser,)\s*\n(\))";
+ var launchReplacement = "$1\n allowed_paths=args.gradio_allowed_paths,\n$2";
+
+ modifiedContent = Regex.Replace(
+ modifiedContent,
+ launchPattern,
+ launchReplacement,
+ RegexOptions.Multiline
+ );
+
+ return modifiedContent;
+ }
+
+ private async Task InstallTritonAndSageAttention(InstalledPackage installedPackage)
+ {
+ if (installedPackage.FullPath is null)
+ return;
+
+ var installSageStep = new InstallSageAttentionStep(
+ DownloadService,
+ PrerequisiteHelper,
+ PyInstallationManager
+ )
+ {
+ InstalledPackage = installedPackage,
+ WorkingDirectory = new DirectoryPath(installedPackage.FullPath),
+ EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables,
+ IsBlackwellGpu =
+ SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu(),
+ };
+
+ var runner = new PackageModificationRunner
+ {
+ ShowDialogOnStart = true,
+ ModificationCompleteMessage = "Triton and SageAttention installed successfully",
+ };
+ EventManager.Instance.OnPackageInstallProgressAdded(runner);
+ await runner.ExecuteSteps([installSageStep]).ConfigureAwait(false);
+ }
+}
diff --git a/StabilityMatrix.Core/Models/Packages/FramePackStudio.cs b/StabilityMatrix.Core/Models/Packages/FramePackStudio.cs
new file mode 100644
index 000000000..55ea5c02a
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Packages/FramePackStudio.cs
@@ -0,0 +1,117 @@
+using System.Collections.ObjectModel;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Text.RegularExpressions;
+using Injectio.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Helper.Cache;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Models.Packages.Config;
+using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Core.Models.Packages;
+
+[RegisterSingleton(Duplicate = DuplicateStrategy.Append)]
+public class FramePackStudio(
+ IGithubApiCache githubApi,
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : FramePack(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
+{
+ public override string Name => "framepack-studio";
+ public override string DisplayName { get; set; } = "FramePack Studio";
+ public override string Author => "colinurbs";
+ public override string RepositoryName => "FramePack-Studio";
+ public override string Blurb =>
+ "FramePack Studio is an AI video generation application based on FramePack that strives to provide everything you need to create high quality video projects.";
+ public override string LicenseType => "Apache-2.0";
+ public override string LicenseUrl => "https://github.com/colinurbs/FramePack-Studio/blob/main/LICENSE";
+ public override string LaunchCommand => "studio.py";
+ public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;
+ public override IEnumerable AvailableSharedFolderMethods =>
+ [SharedFolderMethod.None, SharedFolderMethod.Configuration];
+
+ public override SharedFolderLayout? SharedFolderLayout =>
+ new()
+ {
+ ConfigFileType = ConfigFileType.Json,
+ RelativeConfigPath = new FilePath(".framepack", "settings.json"),
+ Rules =
+ [
+ new SharedFolderLayoutRule
+ {
+ ConfigDocumentPaths = ["lora_dir"],
+ TargetRelativePaths = ["loras"],
+ SourceTypes = [SharedFolderType.Lora],
+ },
+ ],
+ };
+
+ public override IReadOnlyDictionary ExtraLaunchCommands =>
+ new Dictionary();
+ public override IReadOnlyList ExtraLaunchArguments => [];
+
+ public override async Task RunPackage(
+ string installLocation,
+ InstalledPackage installedPackage,
+ RunPackageOptions options,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (installedPackage.PreferredSharedFolderMethod is SharedFolderMethod.Configuration)
+ {
+ var settingsPath = new FilePath(installLocation, ".framepack", "settings.json");
+ if (!settingsPath.Exists)
+ {
+ settingsPath.Create();
+ }
+
+ // set the output_dir and metadata_dir
+ var settingsText = await settingsPath.ReadAllTextAsync(cancellationToken).ConfigureAwait(false);
+ var json = JsonSerializer.Deserialize(settingsText) ?? new JsonObject();
+ json["output_dir"] = SettingsManager
+ .ImagesDirectory.JoinDir(nameof(SharedOutputType.Img2Vid))
+ .ToString();
+ json["metadata_dir"] = SettingsManager
+ .ImagesDirectory.JoinDir(nameof(SharedOutputType.Img2Vid))
+ .ToString();
+
+ await settingsPath
+ .WriteAllTextAsync(
+ JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }),
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ }
+
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
+
+ VenvRunner.RunDetached(
+ [LaunchCommand, .. options.Arguments, .. ExtraLaunchArguments],
+ HandleConsoleOutput,
+ OnExit
+ );
+
+ return;
+
+ void HandleConsoleOutput(ProcessOutput s)
+ {
+ onConsoleOutput?.Invoke(s);
+
+ if (!s.Text.Contains("Running on local URL", StringComparison.OrdinalIgnoreCase))
+ return;
+
+ var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)");
+ var match = regex.Match(s.Text);
+ if (match.Success)
+ WebUrl = match.Value;
+ OnStartupComplete(WebUrl);
+ }
+ }
+}
diff --git a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs
index ac7748506..d61b15c37 100644
--- a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs
+++ b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs
@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Injectio.Attributes;
using NLog;
@@ -19,7 +20,13 @@
namespace StabilityMatrix.Core.Models.Packages;
[RegisterSingleton(Duplicate = DuplicateStrategy.Append)]
-public class InvokeAI : BaseGitPackage
+public class InvokeAI(
+ IGithubApiCache githubApi,
+ ISettingsManager settingsManager,
+ IDownloadService downloadService,
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private const string RelativeRootPath = "invokeai-root";
@@ -33,10 +40,7 @@ public class InvokeAI : BaseGitPackage
public override string Blurb => "Professional Creative Tools for Stable Diffusion";
public override string LaunchCommand => "invokeai-web";
- public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Nightmare;
-
- public override IReadOnlyList ExtraLaunchCommands =>
- ["invokeai-db-maintenance", "invokeai-import-images"];
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;
public override Uri PreviewImageUri =>
new("https://raw.githubusercontent.com/invoke-ai/InvokeAI/main/docs/assets/canvas_preview.png");
@@ -46,14 +50,7 @@ public class InvokeAI : BaseGitPackage
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;
public override string MainBranch => "main";
-
- public InvokeAI(
- IGithubApiCache githubApi,
- ISettingsManager settingsManager,
- IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
- )
- : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { }
+ public override bool ShouldIgnoreBranches => true;
public override Dictionary> SharedFolders =>
new()
@@ -100,6 +97,8 @@ IPrerequisiteHelper prerequisiteHelper
public override IEnumerable AvailableTorchIndices =>
[TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.Rocm, TorchIndex.Mps];
+ public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_12_10;
+
public override TorchIndex GetRecommendedTorchVersion()
{
if (Compat.IsMacOS && Compat.IsArm)
@@ -111,12 +110,17 @@ public override TorchIndex GetRecommendedTorchVersion()
}
public override IEnumerable Prerequisites =>
- [
- PackagePrerequisite.Python310,
- PackagePrerequisite.VcRedist,
- PackagePrerequisite.Git,
- PackagePrerequisite.Node,
- ];
+ [PackagePrerequisite.Python310, PackagePrerequisite.VcRedist, PackagePrerequisite.Git];
+
+ public override Task DownloadPackage(
+ string installLocation,
+ DownloadPackageOptions options,
+ IProgress? progress = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ return Task.CompletedTask;
+ }
public override async Task InstallPackage(
string installLocation,
@@ -127,144 +131,128 @@ public override async Task InstallPackage(
CancellationToken cancellationToken = default
)
{
- // Setup venv
- progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
-
- var venvPath = Path.Combine(installLocation, "venv");
- var exists = Directory.Exists(venvPath);
-
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
- venvRunner.UpdateEnvironmentVariables(env => GetEnvVars(env, installLocation));
-
- progress?.Report(new ProgressReport(-1f, "Installing Package", isIndeterminate: true));
+ // Backup existing files/folders except for known directories
+ try
+ {
+ var excludedNames = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "invokeai-root",
+ "invoke.old",
+ "venv",
+ };
- await SetupAndBuildInvokeFrontend(
- installLocation,
- progress,
- onConsoleOutput,
- venvRunner.EnvironmentVariables
- )
- .ConfigureAwait(false);
+ if (Directory.Exists(installLocation))
+ {
+ var entriesToMove = Directory
+ .EnumerateFileSystemEntries(installLocation)
+ .Where(p => !excludedNames.Contains(Path.GetFileName(p)))
+ .ToList();
- var pipCommandArgs = "-e . --use-pep517 --extra-index-url https://download.pytorch.org/whl/cpu";
+ if (entriesToMove.Count > 0)
+ {
+ var backupFolderName = "invoke.old";
+ var backupFolderPath = Path.Combine(installLocation, backupFolderName);
- var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
- var isLegacyNvidiaGpu =
- torchVersion is TorchIndex.Cuda
- && (
- SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu()
- ?? HardwareHelper.HasLegacyNvidiaGpu()
- );
+ if (Directory.Exists(backupFolderPath) || File.Exists(backupFolderPath))
+ {
+ backupFolderPath = Path.Combine(
+ installLocation,
+ $"invoke.old.{DateTime.Now:yyyyMMddHHmmss}"
+ );
+ }
- var torchInstallArgs = new PipInstallArgs();
+ Directory.CreateDirectory(backupFolderPath);
- switch (torchVersion)
- {
- case TorchIndex.Cuda:
- var torchIndex = isLegacyNvidiaGpu ? "cu126" : "cu128";
- torchInstallArgs = torchInstallArgs
- .WithTorch("==2.7.0")
- .WithTorchVision("==0.22.0")
- .WithTorchAudio("==2.7.0")
- .WithXFormers("==0.0.30")
- .WithTorchExtraIndex(torchIndex);
-
- Logger.Info("Starting InvokeAI install (CUDA)...");
- pipCommandArgs =
- $"-e .[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/{torchIndex}";
- break;
+ foreach (var entry in entriesToMove)
+ {
+ var destinationPath = Path.Combine(backupFolderPath, Path.GetFileName(entry));
- case TorchIndex.Rocm:
- torchInstallArgs = torchInstallArgs
- .WithTorch("==2.2.2")
- .WithTorchVision("==0.17.2")
- .WithExtraIndex("rocm5.6");
+ // Ensure we do not overwrite existing files if names collide
+ if (File.Exists(destinationPath) || Directory.Exists(destinationPath))
+ {
+ var name = Path.GetFileNameWithoutExtension(entry);
+ var ext = Path.GetExtension(entry);
+ var uniqueName = $"{name}_{DateTime.Now:yyyyMMddHHmmss}{ext}";
+ destinationPath = Path.Combine(backupFolderPath, uniqueName);
+ }
- Logger.Info("Starting InvokeAI install (ROCm)...");
- pipCommandArgs =
- "-e . --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.6";
- break;
+ if (Directory.Exists(entry))
+ {
+ Directory.Move(entry, destinationPath);
+ }
+ else if (File.Exists(entry))
+ {
+ File.Move(entry, destinationPath);
+ }
+ }
- case TorchIndex.Mps:
- // For Apple silicon, use MPS
- Logger.Info("Starting InvokeAI install (MPS)...");
- pipCommandArgs = "-e . --use-pep517";
- break;
+ Logger.Info($"Moved {entriesToMove.Count} item(s) to '{backupFolderPath}'.");
+ }
+ }
}
-
- if (installedPackage.PipOverrides != null)
+ catch (Exception e)
{
- torchInstallArgs = torchInstallArgs.WithUserOverrides(installedPackage.PipOverrides);
+ Logger.Warn(e, "Failed to move existing files to 'invoke.old'. Continuing with installation.");
}
- if (torchInstallArgs.Arguments.Count > 0)
- {
- await venvRunner.PipInstall(torchInstallArgs, onConsoleOutput).ConfigureAwait(false);
- }
+ // Setup venv
+ progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
- await venvRunner
- .PipInstall($"{pipCommandArgs}{(exists ? " --upgrade" : "")}", onConsoleOutput)
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
.ConfigureAwait(false);
+ venvRunner.UpdateEnvironmentVariables(env => GetEnvVars(env, installLocation));
- await venvRunner.PipInstall("rich packaging python-dotenv", onConsoleOutput).ConfigureAwait(false);
- progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false));
- }
-
- private async Task SetupAndBuildInvokeFrontend(
- string installLocation,
- IProgress? progress,
- Action? onConsoleOutput,
- IReadOnlyDictionary? envVars = null,
- InstallPackageOptions? installOptions = null
- )
- {
- await PrerequisiteHelper.InstallNodeIfNecessary(progress).ConfigureAwait(false);
+ progress?.Report(new ProgressReport(-1f, "Installing Package", isIndeterminate: true));
- var pnpmVersion = installOptions?.VersionOptions.VersionTag?.Contains("v5") == true ? "8" : "10";
+ var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
+ var isLegacyNvidiaGpu =
+ SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();
+ var fallbackIndex = torchVersion switch
+ {
+ TorchIndex.Cpu when Compat.IsLinux => "https://download.pytorch.org/whl/cpu",
+ TorchIndex.Cuda when isLegacyNvidiaGpu => "https://download.pytorch.org/whl/cu126",
+ TorchIndex.Cuda => "https://download.pytorch.org/whl/cu128",
+ TorchIndex.Rocm => "https://download.pytorch.org/whl/rocm6.2.4",
+ _ => string.Empty,
+ };
- await PrerequisiteHelper
- .RunNpm(["i", $"pnpm@{pnpmVersion}"], installLocation, envVars: envVars)
- .ConfigureAwait(false);
+ var invokeInstallArgs = new PipInstallArgs($"invokeai=={options.VersionOptions.VersionTag}");
- await PrerequisiteHelper
- .RunNpm(["i", "vite", "--ignore-scripts=true"], installLocation, envVars: envVars)
+ var contentStream = await DownloadService
+ .GetContentAsync(
+ $"https://raw.githubusercontent.com/invoke-ai/InvokeAI/refs/tags/{options.VersionOptions.VersionTag}/pins.json",
+ cancellationToken
+ )
.ConfigureAwait(false);
- var pnpmPath = Path.Combine(
- installLocation,
- "node_modules",
- ".bin",
- Compat.IsWindows ? "pnpm.cmd" : "pnpm"
- );
-
- var vitePath = Path.Combine(
- installLocation,
- "node_modules",
- ".bin",
- Compat.IsWindows ? "vite.cmd" : "vite"
- );
-
- var invokeFrontendPath = Path.Combine(installLocation, "invokeai", "frontend", "web");
-
- var process = ProcessRunner.StartAnsiProcess(
- pnpmPath,
- "i --ignore-scripts=true --force",
- invokeFrontendPath,
- onConsoleOutput,
- envVars
- );
-
- await process.WaitForExitAsync().ConfigureAwait(false);
+ // read to json, just deserialize as JObject or whtaever it is in System.Text>json
+ using var reader = new StreamReader(contentStream);
+ var json = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
+ var pins = JsonNode.Parse(json);
+ var platform =
+ Compat.IsWindows ? "win32"
+ : Compat.IsMacOS ? "darwin"
+ : "linux";
+ var index = pins?["torchIndexUrl"]?[platform]?[
+ torchVersion.ToString().ToLowerInvariant()
+ ]?.GetValue();
+
+ if (!string.IsNullOrWhiteSpace(index) && !isLegacyNvidiaGpu)
+ {
+ invokeInstallArgs = invokeInstallArgs.AddArg("--index").AddArg(index);
+ }
+ else if (!string.IsNullOrWhiteSpace(fallbackIndex))
+ {
+ invokeInstallArgs = invokeInstallArgs.AddArg("--index").AddArg(fallbackIndex);
+ }
- process = ProcessRunner.StartAnsiProcess(
- Compat.IsWindows ? pnpmPath : vitePath,
- Compat.IsWindows ? "vite build" : "build",
- invokeFrontendPath,
- onConsoleOutput,
- envVars
- );
+ invokeInstallArgs = invokeInstallArgs.AddArg("--force-reinstall");
- await process.WaitForExitAsync().ConfigureAwait(false);
+ await venvRunner.PipInstall(invokeInstallArgs, onConsoleOutput).ConfigureAwait(false);
+ progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false));
}
public override Task RunPackage(
@@ -298,29 +286,20 @@ private async Task RunInvokeCommand(
throw new InvalidOperationException("Cannot spam 3 if not running detached");
}
- await SetupVenv(installedPackagePath).ConfigureAwait(false);
+ await SetupVenv(installedPackagePath, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
VenvRunner.UpdateEnvironmentVariables(env => GetEnvVars(env, installedPackagePath));
- // fix frontend build missing for people who updated to v3.6 before the fix
- var frontendExistsPath = Path.Combine(installedPackagePath, relativeFrontendBuildPath);
- if (!Directory.Exists(frontendExistsPath))
- {
- await SetupAndBuildInvokeFrontend(
- installedPackagePath,
- null,
- onConsoleOutput,
- VenvRunner.EnvironmentVariables
- )
- .ConfigureAwait(false);
- }
-
// Launch command is for a console entry point, and not a direct script
var entryPoint = await VenvRunner.GetEntryPoint(command).ConfigureAwait(false);
// Split at ':' to get package and function
var split = entryPoint?.Split(':');
+ // Console message because Invoke takes forever to start sometimes with no output of what its doing
+ onConsoleOutput?.Invoke(new ProcessOutput { Text = "Starting InvokeAI...\n" });
+
if (split is not { Length: > 1 })
{
throw new Exception($"Could not find entry point for InvokeAI: {entryPoint.ToRepr()}");
@@ -393,6 +372,42 @@ await SetupInvokeModelSharingConfig(onConsoleOutput, match, s)
}
}
+ public override async Task Update(
+ string installLocation,
+ InstalledPackage installedPackage,
+ UpdatePackageOptions options,
+ IProgress? progress = null,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ await InstallPackage(
+ installLocation,
+ installedPackage,
+ options.AsInstallOptions(),
+ progress,
+ onConsoleOutput,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+
+ if (!string.IsNullOrWhiteSpace(options.VersionOptions.VersionTag))
+ {
+ return new InstalledPackageVersion
+ {
+ InstalledReleaseVersion = options.VersionOptions.VersionTag,
+ IsPrerelease = options.VersionOptions.IsPrerelease,
+ };
+ }
+
+ return new InstalledPackageVersion
+ {
+ InstalledBranch = options.VersionOptions.BranchName,
+ InstalledCommitSha = options.VersionOptions.CommitHash,
+ IsPrerelease = options.VersionOptions.IsPrerelease,
+ };
+ }
+
// Invoke doing shared folders on startup instead
public override Task SetupModelFolders(
DirectoryPath installDirectory,
diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
index 3379b4070..7c061f7f5 100644
--- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
+++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs
@@ -17,8 +17,9 @@ public class KohyaSs(
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper,
- IPyRunner runner
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPyRunner runner,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "kohya_ss";
public override string DisplayName { get; set; } = "kohya_ss";
@@ -58,6 +59,13 @@ IPyRunner runner
Options = ["--port"],
},
new LaunchOptionDefinition
+ {
+ Name = "Skip Requirements Verification",
+ Type = LaunchOptionType.Bool,
+ Options = ["--noverify"],
+ InitialValue = true,
+ },
+ new LaunchOptionDefinition
{
Name = "Username",
Type = LaunchOptionType.String,
@@ -114,48 +122,25 @@ await PrerequisiteHelper
)
.ConfigureAwait(false);
- // make sure long paths are enabled
if (Compat.IsWindows)
{
await PrerequisiteHelper.FixGitLongPaths().ConfigureAwait(false);
}
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
- // Setup venv
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
-
- // Extra dep needed before running setup since v23.0.x
- var pipArgs = new PipInstallArgs("rich", "packaging", "setuptools", "uv");
- if (installedPackage.PipOverrides != null)
- {
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
- }
-
- await venvRunner.PipInstall(pipArgs).ConfigureAwait(false);
-
- var isLegacyNvidia =
- SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();
- var torchExtraIndex = isLegacyNvidia ? "cu126" : "cu128";
-
- // install torch
- pipArgs = new PipInstallArgs()
- .WithTorch()
- .WithTorchVision()
- .WithTorchAudio()
- .WithXFormers(">=0.0.30")
- .WithTorchExtraIndex(torchExtraIndex)
- .AddArg("--force-reinstall");
-
- if (installedPackage.PipOverrides != null)
- {
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
- }
-
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
-
+ // --- Platform-Specific Installation ---
if (Compat.IsLinux)
{
+ // Linux path is too custom for the orchestrator, so it remains as is.
+ await venvRunner
+ .PipInstall("rich packaging setuptools uv", onConsoleOutput)
+ .ConfigureAwait(false);
await venvRunner
.CustomInstall(
[
@@ -166,26 +151,40 @@ await venvRunner
onConsoleOutput
)
.ConfigureAwait(false);
- pipArgs = new PipInstallArgs();
}
else if (Compat.IsWindows)
{
- var requirements = new FilePath(installLocation, "requirements_windows.txt");
- pipArgs = new PipInstallArgs()
- .WithParsedFromRequirementsTxt(
- await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
- "bitsandbytes==0\\.44\\.0"
- )
- .AddArg("bitsandbytes");
- }
+ // Windows path is a perfect fit for the orchestrator.
+ var isLegacyNvidia =
+ SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu()
+ ?? HardwareHelper.HasLegacyNvidiaGpu();
- if (installedPackage.PipOverrides != null)
- {
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
+ var config = new PipInstallConfig
+ {
+ PrePipInstallArgs = ["rich", "packaging", "setuptools", "uv"],
+ RequirementsFilePaths = ["requirements_windows.txt"],
+ // Exclude torch ecosystem (default) AND the specific bitsandbytes version
+ RequirementsExcludePattern =
+ "(torch|torchvision|torchaudio|xformers|bitsandbytes==0\\.44\\.0)",
+ TorchaudioVersion = " ",
+ XformersVersion = " ",
+ CudaIndex = isLegacyNvidia ? "cu126" : "cu128",
+ // Add back the generic bitsandbytes and the specific numpy version
+ ExtraPipArgs = ["bitsandbytes"],
+ PostInstallPipArgs = ["numpy==1.26.4"],
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
}
-
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
- await venvRunner.PipInstall("numpy==1.26.4", onConsoleOutput).ConfigureAwait(false);
}
public override async Task RunPackage(
@@ -196,7 +195,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
void HandleConsoleOutput(ProcessOutput s)
{
diff --git a/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs b/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs
index 6f3ea2883..7cb92841d 100644
--- a/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs
+++ b/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs
@@ -1,6 +1,7 @@
using Injectio.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
+using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
@@ -10,8 +11,9 @@ public class Mashb1tFooocus(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "mashb1t-fooocus";
public override string Author => "mashb1t";
diff --git a/StabilityMatrix.Core/Models/Packages/OneTrainer.cs b/StabilityMatrix.Core/Models/Packages/OneTrainer.cs
index 157b017bd..bc330fa38 100644
--- a/StabilityMatrix.Core/Models/Packages/OneTrainer.cs
+++ b/StabilityMatrix.Core/Models/Packages/OneTrainer.cs
@@ -16,8 +16,9 @@ public class OneTrainer(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "OneTrainer";
public override string DisplayName { get; set; } = "OneTrainer";
@@ -53,7 +54,11 @@ public override async Task InstallPackage(
)
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing requirements", isIndeterminate: true));
var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
@@ -61,17 +66,26 @@ public override async Task InstallPackage(
{
TorchIndex.Cuda => "requirements-cuda.txt",
TorchIndex.Rocm => "requirements-rocm.txt",
- _ => "requirements-default.txt"
+ _ => "requirements-default.txt",
};
await venvRunner.PipInstall(["-r", requirementsFileName], onConsoleOutput).ConfigureAwait(false);
- await venvRunner.PipInstall(["-r", "requirements-global.txt"], onConsoleOutput).ConfigureAwait(false);
+
+ var requirementsGlobal = new FilePath(installLocation, "requirements-global.txt");
+ var pipArgs = new PipInstallArgs().WithParsedFromRequirementsTxt(
+ (await requirementsGlobal.ReadAllTextAsync(cancellationToken).ConfigureAwait(false)).Replace(
+ "-e ",
+ ""
+ ),
+ "scipy==1.15.1; sys_platform != 'win32'"
+ );
if (installedPackage.PipOverrides != null)
{
- var pipArgs = new PipInstallArgs().WithUserOverrides(installedPackage.PipOverrides);
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
+ pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
}
+
+ await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
}
public override async Task RunPackage(
@@ -82,10 +96,11 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
VenvRunner.RunDetached(
- [Path.Combine(installLocation, options.Command ?? LaunchCommand), ..options.Arguments],
+ [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],
onConsoleOutput,
OnExit
);
diff --git a/StabilityMatrix.Core/Models/Packages/Options/PythonPackageOptions.cs b/StabilityMatrix.Core/Models/Packages/Options/PythonPackageOptions.cs
index 19aa4ebce..9c28f0be5 100644
--- a/StabilityMatrix.Core/Models/Packages/Options/PythonPackageOptions.cs
+++ b/StabilityMatrix.Core/Models/Packages/Options/PythonPackageOptions.cs
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
+using StabilityMatrix.Core.Python;
namespace StabilityMatrix.Core.Models.Packages;
@@ -8,4 +9,6 @@ public class PythonPackageOptions
public TorchIndex? TorchIndex { get; set; }
public string? TorchVersion { get; set; }
+
+ public PyVersion? PythonVersion { get; set; }
}
diff --git a/StabilityMatrix.Core/Models/Packages/PipInstallConfig.cs b/StabilityMatrix.Core/Models/Packages/PipInstallConfig.cs
new file mode 100644
index 000000000..885754ed1
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Packages/PipInstallConfig.cs
@@ -0,0 +1,22 @@
+namespace StabilityMatrix.Core.Models.Packages;
+
+///
+/// Configuration for the standard pip installation process.
+///
+public record PipInstallConfig
+{
+ public IEnumerable RequirementsFilePaths { get; init; } = [];
+ public string RequirementsExcludePattern { get; init; } = "(torch|torchvision|torchaudio|xformers)";
+ public IEnumerable PrePipInstallArgs { get; init; } = [];
+ public IEnumerable ExtraPipArgs { get; init; } = [];
+ public IEnumerable PostInstallPipArgs { get; init; } = [];
+ public string TorchVersion { get; init; } = "";
+ public string TorchvisionVersion { get; init; } = "";
+ public string TorchaudioVersion { get; init; } = "";
+ public string XformersVersion { get; init; } = "";
+ public string CudaIndex { get; init; } = "cu129";
+ public string RocmIndex { get; init; } = "rocm6.4";
+ public bool ForceReinstallTorch { get; init; } = true;
+ public bool UpgradePackages { get; init; } = false;
+ public bool SkipTorchInstall { get; init; } = false;
+}
diff --git a/StabilityMatrix.Core/Models/Packages/Reforge.cs b/StabilityMatrix.Core/Models/Packages/Reforge.cs
index d6d83fa4e..7e2723cc1 100644
--- a/StabilityMatrix.Core/Models/Packages/Reforge.cs
+++ b/StabilityMatrix.Core/Models/Packages/Reforge.cs
@@ -1,6 +1,7 @@
using Injectio.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
+using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
@@ -10,8 +11,9 @@ public class Reforge(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "reforge";
public override string Author => "Panchovix";
@@ -22,9 +24,6 @@ IPrerequisiteHelper prerequisiteHelper
public override string LicenseUrl =>
"https://github.com/Panchovix/stable-diffusion-webui-reForge/blob/main/LICENSE.txt";
public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/reforge/preview.webp");
- public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;
- public override bool OfferInOneClickInstaller => false;
-
- public override string Disclaimer =>
- "Development of this package has stopped. It may be removed from Stability Matrix in the future.";
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Recommended;
+ public override bool OfferInOneClickInstaller => true;
}
diff --git a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
index 3d27ee6a7..dd3fb2208 100644
--- a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
+++ b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
@@ -15,8 +15,9 @@ public class RuinedFooocus(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "RuinedFooocus";
public override string DisplayName { get; set; } = "RuinedFooocus";
@@ -38,37 +39,37 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Port",
Type = LaunchOptionType.String,
Description = "Sets the listen port",
- Options = { "--port" }
+ Options = { "--port" },
},
new()
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
- Options = { "--share" }
+ Options = { "--share" },
},
new()
{
Name = "Listen",
Type = LaunchOptionType.String,
Description = "Set the listen interface",
- Options = { "--listen" }
+ Options = { "--listen" },
},
new()
{
Name = "Auth",
Type = LaunchOptionType.String,
Description = "Set credentials username/password",
- Options = { "--auth" }
+ Options = { "--auth" },
},
new()
{
Name = "No Browser",
Type = LaunchOptionType.Bool,
Description = "Do not launch in browser",
- Options = { "--nobrowser" }
+ Options = { "--nobrowser" },
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
];
public override async Task InstallPackage(
@@ -84,7 +85,11 @@ public override async Task InstallPackage(
if (torchVersion == TorchIndex.Cuda)
{
- await using var venvRunner = await SetupVenvPure(installLocation, forceRecreate: true)
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ forceRecreate: true,
+ pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)
+ )
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
@@ -112,13 +117,13 @@ await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),
else
{
await base.InstallPackage(
- installLocation,
- installedPackage,
- options,
- progress,
- onConsoleOutput,
- cancellationToken
- )
+ installLocation,
+ installedPackage,
+ options,
+ progress,
+ onConsoleOutput,
+ cancellationToken
+ )
.ConfigureAwait(false);
}
diff --git a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs
index f9f7be82e..6ba6dbcd1 100644
--- a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs
+++ b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs
@@ -1,4 +1,5 @@
-using Injectio.Attributes;
+using System.Text;
+using Injectio.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Helper.HardwareInfo;
@@ -16,8 +17,9 @@ public class SDWebForge(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : A3WebUI(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : A3WebUI(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "stable-diffusion-webui-forge";
public override string DisplayName { get; set; } = "Stable Diffusion WebUI Forge";
@@ -43,73 +45,98 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
- Options = ["--server-name"]
+ Options = ["--server-name"],
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
- Options = ["--port"]
+ Options = ["--port"],
},
new()
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
- Options = { "--share" }
+ Options = { "--share" },
},
new()
{
Name = "Pin Shared Memory",
Type = LaunchOptionType.Bool,
- Options = { "--pin-shared-memory" }
+ Options = { "--pin-shared-memory" },
+ InitialValue =
+ HardwareHelper.HasNvidiaGpu()
+ && (
+ SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() is false
+ || !HardwareHelper.HasLegacyNvidiaGpu()
+ ),
},
new()
{
Name = "CUDA Malloc",
Type = LaunchOptionType.Bool,
- Options = { "--cuda-malloc" }
+ Options = { "--cuda-malloc" },
+ InitialValue =
+ HardwareHelper.HasNvidiaGpu()
+ && (
+ SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() is false
+ || !HardwareHelper.HasLegacyNvidiaGpu()
+ ),
},
new()
{
Name = "CUDA Stream",
Type = LaunchOptionType.Bool,
- Options = { "--cuda-stream" }
+ Options = { "--cuda-stream" },
+ InitialValue =
+ HardwareHelper.HasNvidiaGpu()
+ && (
+ SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() is false
+ || !HardwareHelper.HasLegacyNvidiaGpu()
+ ),
+ },
+ new()
+ {
+ Name = "Skip Install",
+ Type = LaunchOptionType.Bool,
+ InitialValue = true,
+ Options = ["--skip-install"],
},
new()
{
Name = "Always Offload from VRAM",
Type = LaunchOptionType.Bool,
- Options = ["--always-offload-from-vram"]
+ Options = ["--always-offload-from-vram"],
},
new()
{
Name = "Always GPU",
Type = LaunchOptionType.Bool,
- Options = ["--always-gpu"]
+ Options = ["--always-gpu"],
},
new()
{
Name = "Always CPU",
Type = LaunchOptionType.Bool,
- Options = ["--always-cpu"]
+ Options = ["--always-cpu"],
},
new()
{
Name = "Skip Torch CUDA Test",
Type = LaunchOptionType.Bool,
InitialValue = Compat.IsMacOS,
- Options = ["--skip-torch-cuda-test"]
+ Options = ["--skip-torch-cuda-test"],
},
new()
{
Name = "No half-precision VAE",
Type = LaunchOptionType.Bool,
InitialValue = Compat.IsMacOS,
- Options = ["--no-half-vae"]
+ Options = ["--no-half-vae"],
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
];
public override IEnumerable AvailableTorchIndices =>
@@ -125,47 +152,55 @@ public override async Task InstallPackage(
)
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
-
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
-
- await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
-
- progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
-
- var requirements = new FilePath(installLocation, "requirements_versions.txt");
- var requirementsContent = await requirements
- .ReadAllTextAsync(cancellationToken)
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
.ConfigureAwait(false);
- var pipArgs = new PipInstallArgs("setuptools==69.5.1");
-
- var isBlackwell =
- SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu();
- var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
-
- pipArgs = pipArgs
- .WithTorch(isBlackwell ? string.Empty : "==2.3.1")
- .WithTorchVision(isBlackwell ? string.Empty : "==0.18.1")
- .WithTorchExtraIndex(
- torchVersion switch
- {
- TorchIndex.Cpu => "cpu",
- TorchIndex.Cuda when isBlackwell => "cu128",
- TorchIndex.Cuda => "cu121",
- TorchIndex.Rocm => "rocm5.7",
- TorchIndex.Mps => "cpu",
- _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null)
- }
+ // Dynamically discover all requirements files
+ var requirementsPaths = new List { "requirements_versions.txt" };
+ var extensionsBuiltinDir = new DirectoryPath(installLocation, "extensions-builtin");
+ if (extensionsBuiltinDir.Exists)
+ {
+ requirementsPaths.AddRange(
+ extensionsBuiltinDir
+ .EnumerateFiles("requirements.txt", EnumerationOptionConstants.AllDirectories)
+ .Select(f => Path.GetRelativePath(installLocation, f.ToString()))
);
+ }
- pipArgs = pipArgs.WithParsedFromRequirementsTxt(requirementsContent, excludePattern: "torch");
+ var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
+ var isBlackwell =
+ torchIndex is TorchIndex.Cuda
+ && (SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu());
- if (installedPackage.PipOverrides != null)
+ var config = new PipInstallConfig
{
- pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
- }
+ PrePipInstallArgs = ["joblib"],
+ RequirementsFilePaths = requirementsPaths,
+ TorchVersion = isBlackwell ? "" : "==2.3.1",
+ TorchvisionVersion = isBlackwell ? "" : "==0.18.1",
+ CudaIndex = isBlackwell ? "cu128" : "cu121",
+ RocmIndex = "rocm5.7",
+ ExtraPipArgs =
+ [
+ "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip",
+ ],
+ PostInstallPipArgs = ["numpy==1.26.4"],
+ };
+
+ await StandardPipInstallProcessAsync(
+ venvRunner,
+ options,
+ installedPackage,
+ config,
+ onConsoleOutput,
+ progress,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false));
}
}
diff --git a/StabilityMatrix.Core/Models/Packages/Sdfx.cs b/StabilityMatrix.Core/Models/Packages/Sdfx.cs
index 7eef80a15..d7153c1a7 100644
--- a/StabilityMatrix.Core/Models/Packages/Sdfx.cs
+++ b/StabilityMatrix.Core/Models/Packages/Sdfx.cs
@@ -20,8 +20,9 @@ public class Sdfx(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "sdfx";
public override string DisplayName { get; set; } = "SDFX";
@@ -38,9 +39,12 @@ IPrerequisiteHelper prerequisiteHelper
public override IEnumerable AvailableTorchIndices =>
[TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps];
- public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert;
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;
+ public override bool OfferInOneClickInstaller => false;
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;
public override List LaunchOptions => [LaunchOptionDefinition.Extras];
+ public override string Disclaimer => "This package may no longer receive updates from its author.";
+ public override PackageType PackageType => PackageType.Legacy;
public override SharedFolderLayout SharedFolderLayout =>
new()
@@ -54,37 +58,37 @@ IPrerequisiteHelper prerequisiteHelper
{
SourceTypes = [SharedFolderType.StableDiffusion],
TargetRelativePaths = ["data/models/checkpoints"],
- ConfigDocumentPaths = ["path.models.checkpoints"]
+ ConfigDocumentPaths = ["path.models.checkpoints"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Diffusers],
TargetRelativePaths = ["data/models/diffusers"],
- ConfigDocumentPaths = ["path.models.diffusers"]
+ ConfigDocumentPaths = ["path.models.diffusers"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.VAE],
TargetRelativePaths = ["data/models/vae"],
- ConfigDocumentPaths = ["path.models.vae"]
+ ConfigDocumentPaths = ["path.models.vae"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],
TargetRelativePaths = ["data/models/loras"],
- ConfigDocumentPaths = ["path.models.loras"]
+ ConfigDocumentPaths = ["path.models.loras"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Embeddings],
TargetRelativePaths = ["data/models/embeddings"],
- ConfigDocumentPaths = ["path.models.embeddings"]
+ ConfigDocumentPaths = ["path.models.embeddings"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Hypernetwork],
TargetRelativePaths = ["data/models/hypernetworks"],
- ConfigDocumentPaths = ["path.models.hypernetworks"]
+ ConfigDocumentPaths = ["path.models.hypernetworks"],
},
new SharedFolderLayoutRule
{
@@ -92,40 +96,40 @@ IPrerequisiteHelper prerequisiteHelper
[
SharedFolderType.ESRGAN,
SharedFolderType.RealESRGAN,
- SharedFolderType.SwinIR
+ SharedFolderType.SwinIR,
],
TargetRelativePaths = ["data/models/upscale_models"],
- ConfigDocumentPaths = ["path.models.upscale_models"]
+ ConfigDocumentPaths = ["path.models.upscale_models"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.TextEncoders],
TargetRelativePaths = ["data/models/clip"],
- ConfigDocumentPaths = ["path.models.clip"]
+ ConfigDocumentPaths = ["path.models.clip"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ClipVision],
TargetRelativePaths = ["data/models/clip_vision"],
- ConfigDocumentPaths = ["path.models.clip_vision"]
+ ConfigDocumentPaths = ["path.models.clip_vision"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter],
TargetRelativePaths = ["data/models/controlnet"],
- ConfigDocumentPaths = ["path.models.controlnet"]
+ ConfigDocumentPaths = ["path.models.controlnet"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.GLIGEN],
TargetRelativePaths = ["data/models/gligen"],
- ConfigDocumentPaths = ["path.models.gligen"]
+ ConfigDocumentPaths = ["path.models.gligen"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ApproxVAE],
TargetRelativePaths = ["data/models/vae_approx"],
- ConfigDocumentPaths = ["path.models.vae_approx"]
+ ConfigDocumentPaths = ["path.models.vae_approx"],
},
new SharedFolderLayoutRule
{
@@ -133,18 +137,18 @@ IPrerequisiteHelper prerequisiteHelper
[
SharedFolderType.IpAdapter,
SharedFolderType.IpAdapters15,
- SharedFolderType.IpAdaptersXl
+ SharedFolderType.IpAdaptersXl,
],
TargetRelativePaths = ["data/models/ipadapter"],
- ConfigDocumentPaths = ["path.models.ipadapter"]
+ ConfigDocumentPaths = ["path.models.ipadapter"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.PromptExpansion],
TargetRelativePaths = ["data/models/prompt_expansion"],
- ConfigDocumentPaths = ["path.models.prompt_expansion"]
+ ConfigDocumentPaths = ["path.models.prompt_expansion"],
},
- ]
+ ],
};
public override Dictionary> SharedOutputFolders =>
new() { [SharedOutputType.Text2Img] = new[] { "data/media/output" } };
@@ -156,7 +160,7 @@ IPrerequisiteHelper prerequisiteHelper
PackagePrerequisite.Python310,
PackagePrerequisite.VcRedist,
PackagePrerequisite.Git,
- PackagePrerequisite.Node
+ PackagePrerequisite.Node,
];
public override async Task InstallPackage(
@@ -170,7 +174,11 @@ public override async Task InstallPackage(
{
progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
// Setup venv
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
venvRunner.UpdateEnvironmentVariables(GetEnvVars);
progress?.Report(
@@ -186,7 +194,7 @@ public override async Task InstallPackage(
TorchIndex.DirectMl => "--directml",
TorchIndex.Cpu => "--cpu",
TorchIndex.Mps => "--mac",
- _ => throw new NotSupportedException($"Torch version {torchVersion} is not supported.")
+ _ => throw new NotSupportedException($"Torch version {torchVersion} is not supported."),
};
await venvRunner
@@ -210,7 +218,11 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- var venvRunner = await SetupVenv(installLocation).ConfigureAwait(false);
+ var venvRunner = await SetupVenv(
+ installLocation,
+ pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)
+ )
+ .ConfigureAwait(false);
venvRunner.UpdateEnvironmentVariables(GetEnvVars);
void HandleConsoleOutput(ProcessOutput s)
@@ -230,7 +242,7 @@ void HandleConsoleOutput(ProcessOutput s)
}
venvRunner.RunDetached(
- [Path.Combine(installLocation, options.Command ?? LaunchCommand), "--run", ..options.Arguments],
+ [Path.Combine(installLocation, options.Command ?? LaunchCommand), "--run", .. options.Arguments],
HandleConsoleOutput,
OnExit
);
@@ -254,10 +266,10 @@ private ImmutableDictionary GetEnvVars(ImmutableDictionary "SimpleSDXL";
public override string DisplayName { get; set; } = "SimpleSDXL";
@@ -180,7 +181,11 @@ public override async Task InstallPackage(
CancellationToken cancellationToken = default
)
{
- await using var venvRunner = await SetupVenvPure(installLocation, forceRecreate: true)
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ forceRecreate: true,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
diff --git a/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs b/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs
index 9428dd284..6b4ee0de3 100644
--- a/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs
+++ b/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs
@@ -16,8 +16,9 @@ public class StableDiffusionDirectMl(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : A3WebUI(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : A3WebUI(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -74,7 +75,11 @@ public override async Task InstallPackage(
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
// Setup venv
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
var pipArgs = new PipInstallArgs()
diff --git a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs
index 026239899..0c98b6430 100644
--- a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs
+++ b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs
@@ -20,8 +20,9 @@ public class StableDiffusionUx(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -37,12 +38,14 @@ IPrerequisiteHelper prerequisiteHelper
public override string LaunchCommand => "launch.py";
public override Uri PreviewImageUri =>
new("https://raw.githubusercontent.com/anapnoe/stable-diffusion-webui-ux/master/screenshot.png");
+ public override string Disclaimer => "This package may no longer receive updates from its author.";
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
- public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;
public override IPackageExtensionManager? ExtensionManager => new A3WebUiExtensionManager(this);
+ public override PackageType PackageType => PackageType.Legacy;
public override Dictionary> SharedFolders =>
new()
@@ -62,7 +65,7 @@ IPrerequisiteHelper prerequisiteHelper
[SharedFolderType.ControlNet] = new[] { "models/ControlNet" },
[SharedFolderType.Codeformer] = new[] { "models/Codeformer" },
[SharedFolderType.LDSR] = new[] { "models/LDSR" },
- [SharedFolderType.AfterDetailer] = new[] { "models/adetailer" }
+ [SharedFolderType.AfterDetailer] = new[] { "models/adetailer" },
};
public override Dictionary>? SharedOutputFolders =>
@@ -73,7 +76,7 @@ IPrerequisiteHelper prerequisiteHelper
[SharedOutputType.Img2Img] = new[] { "outputs/img2img-images" },
[SharedOutputType.Text2Img] = new[] { "outputs/txt2img-images" },
[SharedOutputType.Img2ImgGrids] = new[] { "outputs/img2img-grids" },
- [SharedOutputType.Text2ImgGrids] = new[] { "outputs/txt2img-grids" }
+ [SharedOutputType.Text2ImgGrids] = new[] { "outputs/txt2img-grids" },
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
@@ -84,14 +87,14 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
- Options = ["--server-name"]
+ Options = ["--server-name"],
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
- Options = ["--port"]
+ Options = ["--port"],
},
new()
{
@@ -101,44 +104,44 @@ IPrerequisiteHelper prerequisiteHelper
{
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--medvram",
- _ => null
+ _ => null,
},
- Options = ["--lowvram", "--medvram", "--medvram-sdxl"]
+ Options = ["--lowvram", "--medvram", "--medvram-sdxl"],
},
new()
{
Name = "Xformers",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasNvidiaGpu(),
- Options = ["--xformers"]
+ Options = ["--xformers"],
},
new()
{
Name = "API",
Type = LaunchOptionType.Bool,
InitialValue = true,
- Options = ["--api"]
+ Options = ["--api"],
},
new()
{
Name = "Auto Launch Web UI",
Type = LaunchOptionType.Bool,
InitialValue = false,
- Options = ["--autolaunch"]
+ Options = ["--autolaunch"],
},
new()
{
Name = "Skip Torch CUDA Check",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
- Options = ["--skip-torch-cuda-test"]
+ Options = ["--skip-torch-cuda-test"],
},
new()
{
Name = "Skip Python Version Check",
Type = LaunchOptionType.Bool,
InitialValue = true,
- Options = ["--skip-python-version-check"]
+ Options = ["--skip-python-version-check"],
},
new()
{
@@ -147,22 +150,22 @@ IPrerequisiteHelper prerequisiteHelper
Description = "Do not switch the model to 16-bit floats",
InitialValue =
HardwareHelper.PreferRocm() || HardwareHelper.PreferDirectMLOrZluda() || Compat.IsMacOS,
- Options = ["--no-half"]
+ Options = ["--no-half"],
},
new()
{
Name = "Skip SD Model Download",
Type = LaunchOptionType.Bool,
InitialValue = false,
- Options = ["--no-download-sd-model"]
+ Options = ["--no-download-sd-model"],
},
new()
{
Name = "Skip Install",
Type = LaunchOptionType.Bool,
- Options = ["--skip-install"]
+ Options = ["--skip-install"],
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
];
public override IEnumerable AvailableSharedFolderMethods =>
@@ -191,7 +194,11 @@ public override async Task InstallPackage(
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
var pipArgs = new PipInstallArgs();
@@ -247,7 +254,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
void HandleConsoleOutput(ProcessOutput s)
{
@@ -268,8 +276,8 @@ void HandleConsoleOutput(ProcessOutput s)
VenvRunner.RunDetached(
[
Path.Combine(installLocation, options.Command ?? LaunchCommand),
- ..options.Arguments,
- ..ExtraLaunchArguments
+ .. options.Arguments,
+ .. ExtraLaunchArguments,
],
HandleConsoleOutput,
OnExit
@@ -287,7 +295,7 @@ private class A3WebUiExtensionManager(StableDiffusionUx package)
new Uri(
"https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json"
)
- )
+ ),
];
public override async Task> GetManifestExtensionsAsync(
diff --git a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs
index dec153347..5ccdc21f7 100644
--- a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs
+++ b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs
@@ -11,6 +11,7 @@
using StabilityMatrix.Core.Models.Packages.Config;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
+using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
@@ -20,8 +21,9 @@ public class StableSwarm(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private Process? dotnetProcess;
diff --git a/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs b/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs
index a644b5d71..675e1a081 100644
--- a/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs
+++ b/StabilityMatrix.Core/Models/Packages/UnknownPackage.cs
@@ -22,7 +22,8 @@ public class UnknownPackage(ISettingsManager settingsManager) : BasePackage(sett
public override Uri PreviewImageUri => new("");
- public override IReadOnlyList ExtraLaunchCommands => new[] { "test-config", };
+ public override IReadOnlyDictionary ExtraLaunchCommands =>
+ new Dictionary();
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs
index 6419d2896..abfbe9e9f 100644
--- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs
+++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs
@@ -1,7 +1,5 @@
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
-using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Injectio.Attributes;
using NLog;
@@ -24,8 +22,9 @@ public class VladAutomatic(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -42,6 +41,7 @@ IPrerequisiteHelper prerequisiteHelper
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert;
+ public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_12_10;
public override IEnumerable AvailableTorchIndices =>
new[]
@@ -66,105 +66,111 @@ IPrerequisiteHelper prerequisiteHelper
{
SourceTypes = [SharedFolderType.StableDiffusion],
TargetRelativePaths = ["models/Stable-diffusion"],
- ConfigDocumentPaths = ["ckpt_dir"]
+ ConfigDocumentPaths = ["ckpt_dir"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Diffusers],
TargetRelativePaths = ["models/Diffusers"],
- ConfigDocumentPaths = ["diffusers_dir"]
+ ConfigDocumentPaths = ["diffusers_dir"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.VAE],
TargetRelativePaths = ["models/VAE"],
- ConfigDocumentPaths = ["vae_dir"]
+ ConfigDocumentPaths = ["vae_dir"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Embeddings],
TargetRelativePaths = ["models/embeddings"],
- ConfigDocumentPaths = ["embeddings_dir"]
+ ConfigDocumentPaths = ["embeddings_dir"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Hypernetwork],
TargetRelativePaths = ["models/hypernetworks"],
- ConfigDocumentPaths = ["hypernetwork_dir"]
+ ConfigDocumentPaths = ["hypernetwork_dir"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Codeformer],
TargetRelativePaths = ["models/Codeformer"],
- ConfigDocumentPaths = ["codeformer_models_path"]
+ ConfigDocumentPaths = ["codeformer_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.GFPGAN],
TargetRelativePaths = ["models/GFPGAN"],
- ConfigDocumentPaths = ["gfpgan_models_path"]
+ ConfigDocumentPaths = ["gfpgan_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.BSRGAN],
TargetRelativePaths = ["models/BSRGAN"],
- ConfigDocumentPaths = ["bsrgan_models_path"]
+ ConfigDocumentPaths = ["bsrgan_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ESRGAN],
TargetRelativePaths = ["models/ESRGAN"],
- ConfigDocumentPaths = ["esrgan_models_path"]
+ ConfigDocumentPaths = ["esrgan_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.RealESRGAN],
TargetRelativePaths = ["models/RealESRGAN"],
- ConfigDocumentPaths = ["realesrgan_models_path"]
+ ConfigDocumentPaths = ["realesrgan_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ScuNET],
TargetRelativePaths = ["models/ScuNET"],
- ConfigDocumentPaths = ["scunet_models_path"]
+ ConfigDocumentPaths = ["scunet_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.SwinIR],
TargetRelativePaths = ["models/SwinIR"],
- ConfigDocumentPaths = ["swinir_models_path"]
+ ConfigDocumentPaths = ["swinir_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.LDSR],
TargetRelativePaths = ["models/LDSR"],
- ConfigDocumentPaths = ["ldsr_models_path"]
+ ConfigDocumentPaths = ["ldsr_models_path"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.TextEncoders],
TargetRelativePaths = ["models/CLIP"],
- ConfigDocumentPaths = ["clip_models_path"]
+ ConfigDocumentPaths = ["clip_models_path"],
}, // CLIP
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.Lora],
TargetRelativePaths = ["models/Lora"],
- ConfigDocumentPaths = ["lora_dir"]
+ ConfigDocumentPaths = ["lora_dir"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.LyCORIS],
TargetRelativePaths = ["models/LyCORIS"],
- ConfigDocumentPaths = ["lyco_dir"]
+ ConfigDocumentPaths = ["lyco_dir"],
},
new SharedFolderLayoutRule
{
SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter],
TargetRelativePaths = ["models/ControlNet"],
- ConfigDocumentPaths = ["control_net_models_path"]
+ ConfigDocumentPaths = ["control_net_models_path"],
}, // Combined ControlNet/T2I
- ]
+ new SharedFolderLayoutRule
+ {
+ SourceTypes = [SharedFolderType.DiffusionModels],
+ TargetRelativePaths = ["models/UNET"],
+ ConfigDocumentPaths = ["unet_dir"],
+ },
+ ],
};
public override Dictionary>? SharedOutputFolders =>
@@ -189,14 +195,14 @@ IPrerequisiteHelper prerequisiteHelper
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "localhost",
- Options = ["--server-name"]
+ Options = ["--server-name"],
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7860",
- Options = ["--port"]
+ Options = ["--port"],
},
new()
{
@@ -206,75 +212,75 @@ IPrerequisiteHelper prerequisiteHelper
{
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--medvram",
- _ => null
+ _ => null,
},
- Options = ["--lowvram", "--medvram"]
+ Options = ["--lowvram", "--medvram"],
},
new()
{
Name = "Auto-Launch Web UI",
Type = LaunchOptionType.Bool,
- Options = ["--autolaunch"]
+ Options = ["--autolaunch"],
},
new()
{
Name = "Force use of Intel OneAPI XPU backend",
Type = LaunchOptionType.Bool,
- Options = ["--use-ipex"]
+ Options = ["--use-ipex"],
},
new()
{
Name = "Use DirectML if no compatible GPU is detected",
Type = LaunchOptionType.Bool,
- Options = ["--use-directml"]
+ Options = ["--use-directml"],
},
new()
{
Name = "Force use of Nvidia CUDA backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasNvidiaGpu(),
- Options = ["--use-cuda"]
+ Options = ["--use-cuda"],
},
new()
{
Name = "Force use of Intel OneAPI XPU backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.HasIntelGpu(),
- Options = ["--use-ipex"]
+ Options = ["--use-ipex"],
},
new()
{
Name = "Force use of AMD ROCm backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.PreferRocm(),
- Options = ["--use-rocm"]
+ Options = ["--use-rocm"],
},
new()
{
Name = "Force use of ZLUDA backend",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.PreferDirectMLOrZluda(),
- Options = ["--use-zluda"]
+ Options = ["--use-zluda"],
},
new()
{
Name = "CUDA Device ID",
Type = LaunchOptionType.String,
- Options = ["--device-id"]
+ Options = ["--device-id"],
},
new()
{
Name = "API",
Type = LaunchOptionType.Bool,
- Options = ["--api"]
+ Options = ["--api"],
},
new()
{
Name = "Debug Logging",
Type = LaunchOptionType.Bool,
- Options = ["--debug"]
+ Options = ["--debug"],
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
];
public override string MainBranch => "master";
@@ -290,15 +296,29 @@ public override async Task InstallPackage(
{
progress?.Report(new ProgressReport(-1f, "Installing package...", isIndeterminate: true));
// Setup venv
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
- await venvRunner.PipInstall("numpy==1.26.4").ConfigureAwait(false);
+ await venvRunner.PipInstall(["setuptools", "rich", "uv"]).ConfigureAwait(false);
+ if (options.PythonOptions.PythonVersion is { Minor: < 12 })
+ {
+ venvRunner.UpdateEnvironmentVariables(env =>
+ env.SetItem("SETUPTOOLS_USE_DISTUTILS", "setuptools")
+ );
+ }
+ var requirementsContent = await new FilePath(installLocation, "requirements.txt")
+ .ReadAllTextAsync(cancellationToken)
+ .ConfigureAwait(false);
+ var pipArgs = new PipInstallArgs("--upgrade").WithParsedFromRequirementsTxt(requirementsContent);
if (installedPackage.PipOverrides != null)
{
- var pipArgs = new PipInstallArgs().WithUserOverrides(installedPackage.PipOverrides);
- await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
+ pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);
}
+ await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();
switch (torchVersion)
@@ -306,33 +326,33 @@ public override async Task InstallPackage(
// Run initial install
case TorchIndex.Cuda:
await venvRunner
- .CustomInstall("launch.py --use-cuda --debug --test", onConsoleOutput)
+ .CustomInstall("launch.py --use-cuda --debug --test --uv", onConsoleOutput)
.ConfigureAwait(false);
break;
case TorchIndex.Rocm:
await venvRunner
- .CustomInstall("launch.py --use-rocm --debug --test", onConsoleOutput)
+ .CustomInstall("launch.py --use-rocm --debug --test --uv", onConsoleOutput)
.ConfigureAwait(false);
break;
case TorchIndex.DirectMl:
await venvRunner
- .CustomInstall("launch.py --use-directml --debug --test", onConsoleOutput)
+ .CustomInstall("launch.py --use-directml --debug --test --uv", onConsoleOutput)
.ConfigureAwait(false);
break;
case TorchIndex.Zluda:
await venvRunner
- .CustomInstall("launch.py --use-zluda --debug --test", onConsoleOutput)
+ .CustomInstall("launch.py --use-zluda --debug --test --uv", onConsoleOutput)
.ConfigureAwait(false);
break;
case TorchIndex.Ipex:
await venvRunner
- .CustomInstall("launch.py --use-ipex --debug --test", onConsoleOutput)
+ .CustomInstall("launch.py --use-ipex --debug --test --uv", onConsoleOutput)
.ConfigureAwait(false);
break;
default:
// CPU
await venvRunner
- .CustomInstall("launch.py --debug --test", onConsoleOutput)
+ .CustomInstall("launch.py --debug --test --uv", onConsoleOutput)
.ConfigureAwait(false);
break;
}
@@ -340,58 +360,6 @@ await venvRunner
progress?.Report(new ProgressReport(1f, isIndeterminate: false));
}
- public override async Task DownloadPackage(
- string installLocation,
- DownloadPackageOptions options,
- IProgress? progress = null,
- CancellationToken cancellationToken = default
- )
- {
- progress?.Report(
- new ProgressReport(
- -1f,
- message: "Downloading package...",
- isIndeterminate: true,
- type: ProgressType.Download
- )
- );
-
- var installDir = new DirectoryPath(installLocation);
- installDir.Create();
-
- var versionOptions = options.VersionOptions;
-
- if (string.IsNullOrWhiteSpace(versionOptions.BranchName))
- {
- throw new InvalidOperationException("Branch name is required for VladAutomatic");
- }
-
- await PrerequisiteHelper
- .RunGit(
- new[]
- {
- "clone",
- "-b",
- versionOptions.BranchName,
- "https://github.com/vladmandic/automatic",
- installDir.Name
- },
- progress?.AsProcessOutputHandler(),
- installDir.Parent?.FullPath ?? ""
- )
- .ConfigureAwait(false);
- if (!string.IsNullOrWhiteSpace(versionOptions.CommitHash) && !versionOptions.IsLatest)
- {
- await PrerequisiteHelper
- .RunGit(
- new[] { "checkout", versionOptions.CommitHash },
- progress?.AsProcessOutputHandler(),
- installLocation
- )
- .ConfigureAwait(false);
- }
- }
-
public override async Task RunPackage(
string installLocation,
InstalledPackage installedPackage,
@@ -400,7 +368,15 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
+
+ if (PyVersion.Parse(installedPackage.PythonVersion) is { Minor: < 12 })
+ {
+ VenvRunner.UpdateEnvironmentVariables(env =>
+ env.SetItem("SETUPTOOLS_USE_DISTUTILS", "setuptools")
+ );
+ }
void HandleConsoleOutput(ProcessOutput s)
{
@@ -418,7 +394,7 @@ void HandleConsoleOutput(ProcessOutput s)
}
VenvRunner.RunDetached(
- [Path.Combine(installLocation, options.Command ?? LaunchCommand), ..options.Arguments],
+ [Path.Combine(installLocation, options.Command ?? LaunchCommand), "--uv", .. options.Arguments],
HandleConsoleOutput,
OnExit
);
@@ -434,16 +410,19 @@ public override async Task Update(
)
{
var baseUpdateResult = await base.Update(
- installLocation,
- installedPackage,
- options,
- progress,
- onConsoleOutput,
- cancellationToken
- )
+ installLocation,
+ installedPackage,
+ options,
+ progress,
+ onConsoleOutput,
+ cancellationToken
+ )
.ConfigureAwait(false);
- await using var venvRunner = await SetupVenvPure(installedPackage.FullPath!.Unwrap())
+ await using var venvRunner = await SetupVenvPure(
+ installedPackage.FullPath!.Unwrap(),
+ pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)
+ )
.ConfigureAwait(false);
await venvRunner.CustomInstall("launch.py --upgrade --test", onConsoleOutput).ConfigureAwait(false);
@@ -461,7 +440,7 @@ public override async Task Update(
InstalledCommitSha = result
.StandardOutput?.Replace(Environment.NewLine, "")
.Replace("\n", ""),
- IsPrerelease = false
+ IsPrerelease = false,
};
}
catch (Exception e)
@@ -509,18 +488,15 @@ public override async Task> GetManifestExtensionsA
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }
);
- return jsonManifest?.Select(
- entry =>
- new PackageExtension
- {
- Title = entry.Name,
- Author = entry.Long?.Split('/').FirstOrDefault() ?? "Unknown",
- Reference = entry.Url,
- Files = [entry.Url],
- Description = entry.Description,
- InstallType = "git-clone"
- }
- ) ?? Enumerable.Empty();
+ return jsonManifest?.Select(entry => new PackageExtension
+ {
+ Title = entry.Name,
+ Author = entry.Long?.Split('/').FirstOrDefault() ?? "Unknown",
+ Reference = entry.Url,
+ Files = [entry.Url],
+ Description = entry.Description,
+ InstallType = "git-clone",
+ }) ?? Enumerable.Empty();
}
catch (Exception e)
{
diff --git a/StabilityMatrix.Core/Models/Packages/VoltaML.cs b/StabilityMatrix.Core/Models/Packages/VoltaML.cs
index 7c8c5b5d1..df3368caf 100644
--- a/StabilityMatrix.Core/Models/Packages/VoltaML.cs
+++ b/StabilityMatrix.Core/Models/Packages/VoltaML.cs
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions;
using Injectio.Attributes;
+using Python.Runtime;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models.Progress;
@@ -14,8 +15,9 @@ public class VoltaML(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
- IPrerequisiteHelper prerequisiteHelper
-) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
+ IPrerequisiteHelper prerequisiteHelper,
+ IPyInstallationManager pyInstallationManager
+) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager)
{
public override string Name => "voltaML-fast-stable-diffusion";
public override string DisplayName { get; set; } = "VoltaML";
@@ -27,9 +29,10 @@ IPrerequisiteHelper prerequisiteHelper
public override string LaunchCommand => "main.py";
public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/voltaml/preview.webp");
- public override string Disclaimer => "This package may no longer be actively maintained";
- public override PackageDifficulty InstallerSortOrder => PackageDifficulty.UltraNightmare;
+ public override string Disclaimer => "This package may no longer receive updates from its author.";
+ public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;
public override bool OfferInOneClickInstaller => false;
+ public override PackageType PackageType => PackageType.Legacy;
// There are releases but the manager just downloads the latest commit anyways,
// so we'll just limit to commit mode to be more consistent
@@ -77,27 +80,27 @@ IPrerequisiteHelper prerequisiteHelper
"--log-level INFO",
"--log-level WARNING",
"--log-level ERROR",
- "--log-level CRITICAL"
- }
+ "--log-level CRITICAL",
+ },
},
new()
{
Name = "Use ngrok to expose the API",
Type = LaunchOptionType.Bool,
- Options = { "--ngrok" }
+ Options = { "--ngrok" },
},
new()
{
Name = "Expose the API to the network",
Type = LaunchOptionType.Bool,
- Options = { "--host" }
+ Options = { "--host" },
},
new()
{
Name = "Skip virtualenv check",
Type = LaunchOptionType.Bool,
InitialValue = true,
- Options = { "--in-container" }
+ Options = { "--in-container" },
},
new()
{
@@ -110,35 +113,35 @@ IPrerequisiteHelper prerequisiteHelper
"--pytorch-type rocm",
"--pytorch-type directml",
"--pytorch-type intel",
- "--pytorch-type vulkan"
- }
+ "--pytorch-type vulkan",
+ },
},
new()
{
Name = "Run in tandem with the Discord bot",
Type = LaunchOptionType.Bool,
- Options = { "--bot" }
+ Options = { "--bot" },
},
new()
{
Name = "Enable Cloudflare R2 bucket upload support",
Type = LaunchOptionType.Bool,
- Options = { "--enable-r2" }
+ Options = { "--enable-r2" },
},
new()
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "5003",
- Options = { "--port" }
+ Options = { "--port" },
},
new()
{
Name = "Only install requirements and exit",
Type = LaunchOptionType.Bool,
- Options = { "--install-only" }
+ Options = { "--install-only" },
},
- LaunchOptionDefinition.Extras
+ LaunchOptionDefinition.Extras,
};
public override string MainBranch => "main";
@@ -154,7 +157,11 @@ public override async Task InstallPackage(
{
// Setup venv
progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
- await using var venvRunner = await SetupVenvPure(installLocation).ConfigureAwait(false);
+ await using var venvRunner = await SetupVenvPure(
+ installLocation,
+ pythonVersion: options.PythonOptions.PythonVersion
+ )
+ .ConfigureAwait(false);
// Install requirements
progress?.Report(new ProgressReport(-1, "Installing Package Requirements", isIndeterminate: true));
@@ -178,7 +185,8 @@ public override async Task RunPackage(
CancellationToken cancellationToken = default
)
{
- await SetupVenv(installLocation).ConfigureAwait(false);
+ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))
+ .ConfigureAwait(false);
var foundIndicator = false;
@@ -207,7 +215,7 @@ void HandleConsoleOutput(ProcessOutput s)
}
VenvRunner.RunDetached(
- [Path.Combine(installLocation, options.Command ?? LaunchCommand), ..options.Arguments],
+ [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],
HandleConsoleOutput,
OnExit
);
diff --git a/StabilityMatrix.Core/Models/Secrets.cs b/StabilityMatrix.Core/Models/Secrets.cs
index cd2e31889..6ce43c844 100644
--- a/StabilityMatrix.Core/Models/Secrets.cs
+++ b/StabilityMatrix.Core/Models/Secrets.cs
@@ -11,6 +11,8 @@ public readonly record struct Secrets
public CivitApiTokens? CivitApi { get; init; }
public LykosAccountV2Tokens? LykosAccountV2 { get; init; }
+
+ public string? HuggingFaceToken { get; init; }
}
public static class SecretsExtensions
diff --git a/StabilityMatrix.Core/Models/Settings/LastDownloadLocationInfo.cs b/StabilityMatrix.Core/Models/Settings/LastDownloadLocationInfo.cs
new file mode 100644
index 000000000..4a51e0672
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Settings/LastDownloadLocationInfo.cs
@@ -0,0 +1,7 @@
+namespace StabilityMatrix.Core.Models.Settings;
+
+public class LastDownloadLocationInfo
+{
+ public string? SelectedInstallLocation { get; set; }
+ public string? CustomInstallLocation { get; set; }
+}
diff --git a/StabilityMatrix.Core/Models/Settings/Settings.cs b/StabilityMatrix.Core/Models/Settings/Settings.cs
index 0c40ae656..1558037ff 100644
--- a/StabilityMatrix.Core/Models/Settings/Settings.cs
+++ b/StabilityMatrix.Core/Models/Settings/Settings.cs
@@ -3,9 +3,7 @@
using System.Text.Json.Serialization;
using Semver;
using StabilityMatrix.Core.Converters.Json;
-using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper.HardwareInfo;
-using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Update;
namespace StabilityMatrix.Core.Models.Settings;
@@ -121,14 +119,33 @@ public InstalledPackage? PreferredWorkflowPackage
public bool IsDiscordRichPresenceEnabled { get; set; }
+ public HashSet DisabledBaseModelTypes { get; set; } = [];
+
+ public HashSet SavedInferenceDimensions { get; set; } =
+ [
+ "1024 x 1024",
+ "1152 x 896",
+ "1216 x 832",
+ "1280 x 720",
+ "1344 x 768",
+ "1536 x 640",
+ "768 x 768",
+ "512 x 512",
+ "640 x 1536",
+ "768 x 1344",
+ "720 x 1280",
+ "832 x 1216",
+ "896 x 1152",
+ ];
+
[JsonIgnore]
public Dictionary DefaultEnvironmentVariables { get; } =
new()
{
// Fixes potential setuptools error on Portable Windows Python
- ["SETUPTOOLS_USE_DISTUTILS"] = "stdlib",
+ // ["SETUPTOOLS_USE_DISTUTILS"] = "stdlib",
// Suppresses 'A new release of pip is available' messages
- ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
+ ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1",
};
[JsonPropertyName("EnvironmentVariables")]
@@ -139,6 +156,14 @@ public IReadOnlyDictionary EnvironmentVariables
{
get
{
+ // add here when can use GlobalConfig
+ DefaultEnvironmentVariables["UV_CACHE_DIR"] = Path.Combine(
+ GlobalConfig.LibraryDir,
+ "Assets",
+ "uv",
+ "cache"
+ );
+
if (UserEnvironmentVariables is null || UserEnvironmentVariables.Count == 0)
{
return DefaultEnvironmentVariables;
@@ -217,8 +242,18 @@ public IReadOnlyDictionary EnvironmentVariables
public bool FilterExtraNetworksByBaseModel { get; set; } = true;
+ public bool ShowAllAvailablePythonVersions { get; set; }
+
public bool IsMainWindowSidebarOpen { get; set; }
-
+
+ public Dictionary ModelTypeDownloadPreferences { get; set; } = new();
+
+ public bool ShowTrainingDataInModelBrowser { get; set; }
+
+ public string? CivitModelBrowserFileNamePattern { get; set; }
+
+ public int InferenceDimensionStepChange { get; set; } = 128;
+
[JsonIgnore]
public bool IsHolidayModeActive =>
HolidayModeSetting == HolidayMode.Automatic
@@ -295,4 +330,6 @@ public static CultureInfo GetDefaultCulture()
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(string))]
+[JsonSerializable(typeof(LastDownloadLocationInfo))]
+[JsonSerializable(typeof(Dictionary))]
internal partial class SettingsSerializerContext : JsonSerializerContext;
diff --git a/StabilityMatrix.Core/Models/SharedOutputType.cs b/StabilityMatrix.Core/Models/SharedOutputType.cs
index c2b7b0820..c49d4d28a 100644
--- a/StabilityMatrix.Core/Models/SharedOutputType.cs
+++ b/StabilityMatrix.Core/Models/SharedOutputType.cs
@@ -6,10 +6,11 @@ public enum SharedOutputType
Text2Img,
Text2Vid,
Img2Img,
+ Img2Vid,
Extras,
Text2ImgGrids,
Img2ImgGrids,
SVD,
Saved,
- Consolidated
+ Consolidated,
}
diff --git a/StabilityMatrix.Core/Models/UnknownInstalledPackage.cs b/StabilityMatrix.Core/Models/UnknownInstalledPackage.cs
index 3d9360bc1..998303a8d 100644
--- a/StabilityMatrix.Core/Models/UnknownInstalledPackage.cs
+++ b/StabilityMatrix.Core/Models/UnknownInstalledPackage.cs
@@ -1,4 +1,5 @@
using StabilityMatrix.Core.Models.Packages;
+using StabilityMatrix.Core.Python;
namespace StabilityMatrix.Core.Models;
@@ -11,6 +12,7 @@ public static UnknownInstalledPackage FromDirectoryName(string name)
Id = Guid.NewGuid(),
PackageName = UnknownPackage.Key,
DisplayName = name,
+ PythonVersion = PyInstallationManager.Python_3_10_17.StringValue,
LibraryPath = $"Packages{System.IO.Path.DirectorySeparatorChar}{name}",
};
}
diff --git a/StabilityMatrix.Core/Processes/ProcessRunner.cs b/StabilityMatrix.Core/Processes/ProcessRunner.cs
index 1e054a838..3c146ff64 100644
--- a/StabilityMatrix.Core/Processes/ProcessRunner.cs
+++ b/StabilityMatrix.Core/Processes/ProcessRunner.cs
@@ -259,6 +259,141 @@ public static async Task GetProcessResultAsync(
};
}
+ ///
+ /// Starts a process, captures its output in real-time via a callback, and returns the final process result.
+ ///
+ /// The name of the file to execute.
+ /// The command-line arguments to pass to the executable.
+ /// The working directory for the process.
+ /// Callback that receives process output in real-time.
+ /// Environment variables to set for the process.
+ /// Cancellation token to cancel waiting for process exit and close the process.
+ /// A ProcessResult containing the exit code and combined output.
+ public static async Task GetAnsiProcessResultAsync(
+ string fileName,
+ ProcessArgs arguments,
+ string? workingDirectory = null,
+ Action? outputDataReceived = null,
+ IReadOnlyDictionary? environmentVariables = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ Logger.Debug($"Starting process '{fileName}' with arguments '{arguments}'");
+
+ var info = new ProcessStartInfo
+ {
+ FileName = fileName,
+ Arguments = arguments,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true,
+ };
+
+ if (environmentVariables != null)
+ {
+ foreach (var (key, value) in environmentVariables)
+ {
+ info.EnvironmentVariables[key] = value;
+ }
+ }
+
+ if (workingDirectory != null)
+ {
+ info.WorkingDirectory = workingDirectory;
+ }
+
+ var stdoutBuilder = new StringBuilder();
+ var stderrBuilder = new StringBuilder();
+
+ using var process = new AnsiProcess(info);
+ StartTrackedProcess(process);
+
+ try
+ {
+ if (outputDataReceived != null)
+ {
+ process.BeginAnsiRead(output =>
+ {
+ // Call the user's callback
+ outputDataReceived(output);
+
+ // Also capture the output for the final result
+ if (output.IsStdErr)
+ {
+ stderrBuilder.AppendLine(output.Text);
+ }
+ else
+ {
+ stdoutBuilder.AppendLine(output.Text);
+ }
+ });
+ }
+
+ await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
+
+ // Ensure we've processed all output
+ if (outputDataReceived != null)
+ {
+ await process.WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false);
+ }
+
+ string? processName = null;
+ var elapsed = TimeSpan.Zero;
+
+ // Accessing these properties may throw an exception if the process has already exited
+ try
+ {
+ processName = process.ProcessName;
+ }
+ catch (SystemException) { }
+
+ try
+ {
+ elapsed = process.ExitTime - process.StartTime;
+ }
+ catch (SystemException) { }
+
+ return new ProcessResult
+ {
+ ExitCode = process.ExitCode,
+ StandardOutput = stdoutBuilder.ToString(),
+ StandardError = stderrBuilder.ToString(),
+ ProcessName = processName,
+ Elapsed = elapsed,
+ };
+ }
+ catch (OperationCanceledException e)
+ {
+ // Handle cancellation
+ Logger.Info($"Process '{fileName}' was cancelled. Killing the process.");
+ process.CancelStreamReaders();
+ process.Kill(true);
+
+ var result = new ProcessResult
+ {
+ ExitCode = process.ExitCode,
+ StandardOutput = stdoutBuilder.ToString(),
+ StandardError = stderrBuilder.ToString(),
+ };
+
+ // Accessing these properties may throw an exception if the process has already exited
+ try
+ {
+ result = result with { ProcessName = process.ProcessName };
+ }
+ catch (SystemException) { }
+
+ try
+ {
+ result = result with { Elapsed = process.ExitTime - process.StartTime };
+ }
+ catch (SystemException) { }
+
+ throw new OperationCanceledException(e.Message, new ProcessException(result), cancellationToken);
+ }
+ }
+
public static Process StartProcess(
string fileName,
string arguments,
diff --git a/StabilityMatrix.Core/Python/IPyInstallationManager.cs b/StabilityMatrix.Core/Python/IPyInstallationManager.cs
new file mode 100644
index 000000000..81c5e0e38
--- /dev/null
+++ b/StabilityMatrix.Core/Python/IPyInstallationManager.cs
@@ -0,0 +1,28 @@
+namespace StabilityMatrix.Core.Python;
+
+///
+/// Interface for managing Python installations
+///
+public interface IPyInstallationManager
+{
+ ///
+ /// Gets all discoverable Python installations (legacy and UV-managed).
+ /// This is now an async method.
+ ///
+ Task> GetAllInstallationsAsync();
+
+ ///
+ /// Gets an installation for a specific version.
+ /// If not found, and UV is configured, it may attempt to install it using UV.
+ /// This is now an async method.
+ ///
+ Task GetInstallationAsync(PyVersion version);
+
+ ///
+ /// Gets the default installation.
+ /// This is now an async method.
+ ///
+ Task GetDefaultInstallationAsync();
+
+ Task> GetAllAvailablePythonsAsync();
+}
diff --git a/StabilityMatrix.Core/Python/IPyRunner.cs b/StabilityMatrix.Core/Python/IPyRunner.cs
index b3e8bf737..237b3d54c 100644
--- a/StabilityMatrix.Core/Python/IPyRunner.cs
+++ b/StabilityMatrix.Core/Python/IPyRunner.cs
@@ -1,64 +1,93 @@
-namespace StabilityMatrix.Core.Python;
+using Python.Runtime;
+using StabilityMatrix.Core.Python.Interop;
+
+namespace StabilityMatrix.Core.Python;
public interface IPyRunner
{
+ PyIOStream? StdOutStream { get; }
+ PyIOStream? StdErrStream { get; }
+
///
/// Initializes the Python runtime using the embedded dll.
- /// Can be called with no effect after initialization.
///
- /// Thrown if Python DLL not found.
Task Initialize();
+ ///
+ /// Switch to a specific Python installation
+ ///
+ Task SwitchToInstallation(PyVersion version);
+
///
/// One-time setup for get-pip
///
- Task SetupPip();
+ Task SetupPip(PyVersion? version = null);
///
/// Install a Python package with pip
///
- Task InstallPackage(string package);
+ Task InstallPackage(string package, PyVersion? version = null);
///
/// Run a Function with PyRunning lock as a Task with GIL.
///
- /// Function to run.
- /// Time limit for waiting on PyRunning lock.
- /// Cancellation token.
- /// cancelToken was canceled, or waitTimeout expired.
- Task RunInThreadWithLock(Func func, TimeSpan? waitTimeout = null,
- CancellationToken cancelToken = default);
+ Task RunInThreadWithLock(
+ Func func,
+ TimeSpan? waitTimeout = null,
+ CancellationToken cancelToken = default
+ );
///
/// Run an Action with PyRunning lock as a Task with GIL.
///
- /// Action to run.
- /// Time limit for waiting on PyRunning lock.
- /// Cancellation token.
- /// cancelToken was canceled, or waitTimeout expired.
- Task RunInThreadWithLock(Action action, TimeSpan? waitTimeout = null,
- CancellationToken cancelToken = default);
+ Task RunInThreadWithLock(
+ Action action,
+ TimeSpan? waitTimeout = null,
+ CancellationToken cancelToken = default
+ );
///
/// Evaluate Python expression and return its value as a string
///
- ///
Task Eval(string expression);
///
/// Evaluate Python expression and return its value
///
- ///
Task Eval(string expression);
///
/// Execute Python code without returning a value
///
- ///
Task Exec(string code);
///
/// Return the Python version as a PyVersionInfo struct
///
Task GetVersionInfo();
+
+ ///
+ /// Get Python directory name for the given version
+ ///
+ string GetPythonDirName(PyVersion? version = null);
+
+ ///
+ /// Get Python directory for the given version
+ ///
+ string GetPythonDir(PyVersion? version = null);
+
+ ///
+ /// Get Python DLL path for the given version
+ ///
+ string GetPythonDllPath(PyVersion? version = null);
+
+ ///
+ /// Get Python executable path for the given version
+ ///
+ string GetPythonExePath(PyVersion? version = null);
+
+ ///
+ /// Get Pip executable path for the given version
+ ///
+ string GetPipExePath(PyVersion? version = null);
}
diff --git a/StabilityMatrix.Core/Python/IPyVenvRunner.cs b/StabilityMatrix.Core/Python/IPyVenvRunner.cs
new file mode 100644
index 000000000..6c1600b77
--- /dev/null
+++ b/StabilityMatrix.Core/Python/IPyVenvRunner.cs
@@ -0,0 +1,130 @@
+using System.Collections.Immutable;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Processes;
+
+namespace StabilityMatrix.Core.Python;
+
+public interface IPyVenvRunner
+{
+ PyBaseInstall BaseInstall { get; }
+
+ ///
+ /// The process running the python executable.
+ ///
+ AnsiProcess? Process { get; }
+
+ ///
+ /// The path to the venv root directory.
+ ///
+ DirectoryPath RootPath { get; }
+
+ ///
+ /// Optional working directory for the python process.
+ ///
+ DirectoryPath? WorkingDirectory { get; set; }
+
+ ///
+ /// Optional environment variables for the python process.
+ ///
+ ImmutableDictionary EnvironmentVariables { get; set; }
+
+ ///
+ /// The full path to the python executable.
+ ///
+ FilePath PythonPath { get; }
+
+ ///
+ /// The full path to the pip executable.
+ ///
+ FilePath PipPath { get; }
+
+ ///
+ /// The Python version of this venv
+ ///
+ PyVersion Version { get; }
+
+ ///
+ /// List of substrings to suppress from the output.
+ /// When a line contains any of these substrings, it will not be forwarded to callbacks.
+ /// A corresponding Info log will be written instead.
+ ///
+ List SuppressOutput { get; }
+
+ void UpdateEnvironmentVariables(
+ Func, ImmutableDictionary> env
+ );
+
+ /// True if the venv has a Scripts\python.exe file
+ bool Exists();
+
+ ///
+ /// Creates a venv at the configured path.
+ ///
+ Task Setup(
+ bool existsOk = false,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Run a pip install command. Waits for the process to exit.
+ /// workingDirectory defaults to RootPath.
+ ///
+ Task PipInstall(ProcessArgs args, Action? outputDataReceived = null);
+
+ ///
+ /// Run a pip uninstall command. Waits for the process to exit.
+ /// workingDirectory defaults to RootPath.
+ ///
+ Task PipUninstall(ProcessArgs args, Action? outputDataReceived = null);
+
+ ///
+ /// Run a pip list command, return results as PipPackageInfo objects.
+ ///
+ Task> PipList();
+
+ ///
+ /// Run a pip show command, return results as PipPackageInfo objects.
+ ///
+ Task PipShow(string packageName);
+
+ ///
+ /// Run a pip index command, return result as PipIndexResult.
+ ///
+ Task PipIndex(string packageName, string? indexUrl = null);
+
+ ///
+ /// Run a custom install command. Waits for the process to exit.
+ /// workingDirectory defaults to RootPath.
+ ///
+ Task CustomInstall(ProcessArgs args, Action? outputDataReceived = null);
+
+ ///
+ /// Run a command using the venv Python executable and return the result.
+ ///
+ /// Arguments to pass to the Python executable.
+ Task Run(ProcessArgs arguments);
+
+ void RunDetached(
+ ProcessArgs args,
+ Action? outputDataReceived,
+ Action? onExit = null,
+ bool unbuffered = true
+ );
+
+ ///
+ /// Get entry points for a package.
+ /// https://packaging.python.org/en/latest/specifications/entry-points/#entry-points
+ ///
+ Task GetEntryPoint(string entryPointName);
+
+ ///
+ /// Kills the running process and cancels stream readers, does not wait for exit.
+ ///
+ void Dispose();
+
+ ///
+ /// Kills the running process, waits for exit.
+ ///
+ ValueTask DisposeAsync();
+}
diff --git a/StabilityMatrix.Core/Python/IUvManager.cs b/StabilityMatrix.Core/Python/IUvManager.cs
new file mode 100644
index 000000000..3832bd6dd
--- /dev/null
+++ b/StabilityMatrix.Core/Python/IUvManager.cs
@@ -0,0 +1,43 @@
+using StabilityMatrix.Core.Processes;
+
+namespace StabilityMatrix.Core.Python;
+
+public interface IUvManager
+{
+ Task IsUvAvailableAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Lists Python distributions known to UV.
+ ///
+ /// If true, only lists Pythons UV reports as installed.
+ /// Optional callback for console output.
+ /// Cancellation token.
+ /// A list of UvPythonInfo objects.
+ Task> ListAvailablePythonsAsync(
+ bool installedOnly = false,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Gets information about a specific installed Python version managed by UV.
+ ///
+ Task GetInstalledPythonAsync(
+ PyVersion version,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Installs a specific Python version using UV.
+ ///
+ /// Python version to install (e.g., "3.10" or "3.10.13").
+ /// Optional. If provided, UV_PYTHON_INSTALL_DIR will be set for the uv process.
+ /// Optional callback for console output.
+ /// Cancellation token.
+ /// UvPythonInfo for the installed Python, or null if installation failed or info couldn't be retrieved.
+ Task InstallPythonVersionAsync(
+ PyVersion version,
+ Action? onConsoleOutput = null,
+ CancellationToken cancellationToken = default
+ );
+}
diff --git a/StabilityMatrix.Core/Python/PyBaseInstall.cs b/StabilityMatrix.Core/Python/PyBaseInstall.cs
index bb12ff5fa..1da4a1117 100644
--- a/StabilityMatrix.Core/Python/PyBaseInstall.cs
+++ b/StabilityMatrix.Core/Python/PyBaseInstall.cs
@@ -1,250 +1,153 @@
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using NLog;
-using StabilityMatrix.Core.Exceptions;
+using NLog;
using StabilityMatrix.Core.Helper;
-using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
-using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Core.Python;
-public class PyBaseInstall(DirectoryPath rootPath, MajorMinorVersion? version = null)
+///
+/// Represents a base Python installation that can be used by PyVenvRunner
+///
+public class PyBaseInstall(PyInstallation installation)
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
- private readonly Lazy _lazyVersion =
- version != null
- ? new Lazy(version.Value)
- : new Lazy(() => FindPythonVersion(rootPath));
-
///
- /// Root path of the Python installation.
+ /// Gets a PyBaseInstall instance for the default Python installation.
+ /// This uses the default Python 3.10.16 installation.
///
- public DirectoryPath RootPath { get; } = rootPath;
+ public static PyBaseInstall Default => new(new PyInstallation(PyInstallationManager.DefaultVersion));
///
- /// Whether this is a portable Windows installation.
- /// Path structure is different.
+ /// The Python installation
///
- public bool IsWindowsPortable { get; init; }
+ public PyInstallation Installation { get; } = installation;
///
- /// Major and minor version of the Python installation.
- /// Set in the constructor or lazily queried via .
+ /// Root path of the Python installation
///
- public MajorMinorVersion Version => _lazyVersion.Value;
-
- public FilePath PythonExePath =>
- Compat.Switch(
- (PlatformKind.Windows, RootPath.JoinFile("python.exe")),
- (PlatformKind.Linux, RootPath.JoinFile("bin", $"python{Version.Major}")),
- (PlatformKind.MacOS, RootPath.JoinFile("bin", $"python{Version.Major}"))
- );
-
- public string DefaultTclTkPath =>
- Compat.Switch(
- (PlatformKind.Windows, RootPath.JoinFile("tcl", "tcl8.6")),
- (PlatformKind.Linux, RootPath.JoinFile("lib", "tcl8.6")),
- (PlatformKind.MacOS, RootPath.JoinFile("lib", "tcl8.6"))
- );
-
- public static PyBaseInstall Default { get; } = new(PyRunner.PythonDir, new MajorMinorVersion(3, 10));
-
- // Attempt to find the major and minor version of the Python installation.
- private static MajorMinorVersion FindPythonVersion(
- DirectoryPath rootPath,
- PlatformKind platform = default
- )
- {
- if (platform == default)
- {
- platform = Compat.Platform;
- }
-
- var searchPath = rootPath;
- string glob;
- Regex regex;
+ public string RootPath => Installation.InstallPath;
- if (platform.HasFlag(PlatformKind.Windows))
- {
- glob = "python*.dll";
- regex = new Regex(@"python(\d)(\d+)\.dll");
- }
- else if (platform.HasFlag(PlatformKind.MacOS))
- {
- searchPath = rootPath.JoinDir("lib");
- glob = "libpython*.*.dylib";
- regex = new Regex(@"libpython(\d+)\.(\d+).dylib");
- }
- else if (platform.HasFlag(PlatformKind.Linux))
- {
- searchPath = rootPath.JoinDir("lib");
- glob = "libpython*.*.so";
- regex = new Regex(@"libpython(\d+)\.(\d+).so");
- }
- else
- {
- throw new NotSupportedException("Unsupported platform");
- }
+ ///
+ /// Python executable path
+ ///
+ public string PythonExePath => Installation.PythonExePath;
- var globResults = rootPath.EnumerateFiles(glob).ToList();
- if (globResults.Count == 0)
- {
- throw new FileNotFoundException("Python library file not found", searchPath + glob);
- }
+ ///
+ /// Pip executable path
+ ///
+ public string PipExePath => Installation.PipExePath;
- // Get first matching file
- var match = globResults.Select(path => regex.Match(path.Name)).FirstOrDefault(x => x.Success);
- if (match is null)
- {
- throw new FileNotFoundException(
- $"Python library file not found with pattern '{regex}'",
- searchPath + glob
- );
- }
+ ///
+ /// Version of the Python installation
+ ///
+ public PyVersion Version => Installation.Version;
- return new(int.Parse(match.Groups[1].Value), int.Parse(match.Groups[2].Value));
- }
+ public bool UsesUv => Installation.UsesUv;
///
- /// Creates a new virtual environment runner.
+ /// Create a virtual environment with this Python installation as the base and
+ /// configure it with the specified parameters.
///
- /// Root path of the venv
- /// Working directory of the venv
- /// Extra environment variables to set
- /// Extra environment variables to set at the end
- /// Whether to include the Tcl/Tk library paths via
- public PyVenvRunner CreateVenvRunner(
+ /// Path where the virtual environment will be created
+ /// Optional working directory for the Python process
+ /// Optional environment variables for the Python process
+ /// Whether to set up the default Tkinter environment variables (Windows)
+ /// Whether to query and set up Tkinter environment variables (Unix)
+ /// A configured PyVenvRunner instance
+ public IPyVenvRunner CreateVenvRunner(
DirectoryPath venvPath,
DirectoryPath? workingDirectory = null,
IReadOnlyDictionary? environmentVariables = null,
- IReadOnlyDictionary? overrideEnvironmentVariables = null,
- bool withDefaultTclTkEnv = false
+ bool withDefaultTclTkEnv = false,
+ bool withQueriedTclTkEnv = false
)
{
- var runner = new PyVenvRunner(this, venvPath) { WorkingDirectory = workingDirectory };
+ IPyVenvRunner venvRunner = new UvVenvRunner(this, venvPath);
- if (environmentVariables is { Count: > 0 })
+ // Set working directory if provided
+ if (workingDirectory != null)
{
- runner.EnvironmentVariables = runner.EnvironmentVariables.AddRange(environmentVariables);
+ venvRunner.WorkingDirectory = workingDirectory;
}
- if (withDefaultTclTkEnv)
+ // Set environment variables if provided
+ if (environmentVariables != null)
{
- runner.EnvironmentVariables = runner.EnvironmentVariables.SetItem(
- "TCL_LIBRARY",
- DefaultTclTkPath
- );
- runner.EnvironmentVariables = runner.EnvironmentVariables.SetItem("TK_LIBRARY", DefaultTclTkPath);
+ var envVarDict = venvRunner.EnvironmentVariables;
+ foreach (var (key, value) in environmentVariables)
+ {
+ envVarDict = envVarDict.SetItem(key, value);
+ }
+
+ if (
+ Version == PyInstallationManager.Python_3_10_11
+ && !envVarDict.ContainsKey("SETUPTOOLS_USE_DISTUTILS")
+ )
+ {
+ // Fixes potential setuptools error on Portable Windows Python
+ envVarDict = envVarDict.SetItem("SETUPTOOLS_USE_DISTUTILS", "stdlib");
+ }
+
+ venvRunner.EnvironmentVariables = envVarDict;
}
- if (overrideEnvironmentVariables is { Count: > 0 })
+ // Configure Tkinter environment variables if requested
+ if (withDefaultTclTkEnv && Compat.IsWindows)
+ {
+ // Set up default TCL/TK environment variables for Windows
+ var envVarDict = venvRunner.EnvironmentVariables;
+ envVarDict = envVarDict.SetItem("TCL_LIBRARY", Path.Combine(RootPath, "tcl", "tcl8.6"));
+ envVarDict = envVarDict.SetItem("TK_LIBRARY", Path.Combine(RootPath, "tcl", "tk8.6"));
+ venvRunner.EnvironmentVariables = envVarDict;
+ }
+ else if (withQueriedTclTkEnv && Compat.IsUnix)
{
- runner.EnvironmentVariables = runner.EnvironmentVariables.AddRange(overrideEnvironmentVariables);
+ // For Unix, we might need to query the system for TCL/TK locations
+ try
+ {
+ // Implementation would depend on how your system detects TCL/TK on Unix
+ Logger.Debug("Setting up TCL/TK environment for Unix");
+ // This would be implemented based on your system's requirements
+ }
+ catch (Exception ex)
+ {
+ Logger.Warn(ex, "Failed to set up TCL/TK environment for Unix");
+ }
}
- return runner;
+ return venvRunner;
}
///
- /// Creates a new virtual environment runner.
+ /// Asynchronously create a virtual environment with this Python installation as the base and
+ /// configure it with the specified parameters.
///
- /// Root path of the venv
- /// Working directory of the venv
- /// Extra environment variables to set
- /// Extra environment variables to set at the end
- /// Whether to include the Tcl/Tk library paths via
- /// Whether to include the Tcl/Tk library paths via
- public async Task CreateVenvRunnerAsync(
- DirectoryPath venvPath,
- DirectoryPath? workingDirectory = null,
+ /// Path where the virtual environment will be created
+ /// Optional working directory for the Python process
+ /// Optional environment variables for the Python process
+ /// Whether to set up the default Tkinter environment variables (Windows)
+ /// Whether to query and set up Tkinter environment variables (Unix)
+ /// A configured PyVenvRunner instance
+ public async Task CreateVenvRunnerAsync(
+ string venvPath,
+ string? workingDirectory = null,
IReadOnlyDictionary? environmentVariables = null,
- IReadOnlyDictionary? overrideEnvironmentVariables = null,
bool withDefaultTclTkEnv = false,
bool withQueriedTclTkEnv = false
)
{
- var runner = CreateVenvRunner(
- venvPath: venvPath,
- workingDirectory: workingDirectory,
- environmentVariables: environmentVariables,
- overrideEnvironmentVariables: null,
- withDefaultTclTkEnv: withDefaultTclTkEnv
+ var dirPath = new DirectoryPath(venvPath);
+ var workingDir = workingDirectory != null ? new DirectoryPath(workingDirectory) : null;
+
+ // Use the synchronous version and just return with a completed task
+ var venvRunner = CreateVenvRunner(
+ dirPath,
+ workingDir,
+ environmentVariables,
+ withDefaultTclTkEnv,
+ withQueriedTclTkEnv
);
- if (withQueriedTclTkEnv)
- {
- var queryResult = await TryQueryTclTkLibraryAsync().ConfigureAwait(false);
- if (queryResult is { Result: { } result })
- {
- if (!string.IsNullOrEmpty(result.TclLibrary))
- {
- runner.EnvironmentVariables = runner.EnvironmentVariables.SetItem(
- "TCL_LIBRARY",
- result.TclLibrary
- );
- }
- if (!string.IsNullOrEmpty(result.TkLibrary))
- {
- runner.EnvironmentVariables = runner.EnvironmentVariables.SetItem(
- "TK_LIBRARY",
- result.TkLibrary
- );
- }
- }
- else
- {
- Logger.Error(queryResult.Exception, "Failed to query Tcl/Tk library paths");
- }
- }
-
- if (overrideEnvironmentVariables is { Count: > 0 })
- {
- runner.EnvironmentVariables = runner.EnvironmentVariables.AddRange(overrideEnvironmentVariables);
- }
-
- return runner;
- }
-
- public async Task> TryQueryTclTkLibraryAsync()
- {
- var processResult = await QueryTclTkLibraryPathAsync().ConfigureAwait(false);
-
- if (!processResult.IsSuccessExitCode || string.IsNullOrEmpty(processResult.StandardOutput))
- {
- return TaskResult.FromException(new ProcessException(processResult));
- }
-
- try
- {
- var result = JsonSerializer.Deserialize(
- processResult.StandardOutput,
- QueryTclTkLibraryResultJsonContext.Default.QueryTclTkLibraryResult
- );
-
- return new TaskResult(result!);
- }
- catch (JsonException e)
- {
- return TaskResult