diff --git a/Avalonia.Gif/Avalonia.Gif.csproj b/Avalonia.Gif/Avalonia.Gif.csproj
index 858b974e..6a363589 100644
--- a/Avalonia.Gif/Avalonia.Gif.csproj
+++ b/Avalonia.Gif/Avalonia.Gif.csproj
@@ -10,7 +10,7 @@
true
-
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69c61c94..c3acb129 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,73 @@ 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.9.0
+### Added
+- Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI
+- Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel
+- Added extension management for SD.Next and Stable Diffusion WebUI-UX
+- Added the ability to choose where CivitAI model downloads are saved
+- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`)
+- Added more Base Model search options to the CivitAI Model Browser
+- Added Stable Cascade to the HuggingFace Model Browser
+#### Inference
+- Added Inference Prompt Styles, with Prompt Expansion model support (i.e. Fooocus V2)
+- Added option to load a .yaml config file next to the model with the same name. Can be used with VPred and other models that require a config file.
+- Added copy image support on linux and macOS for Inference outputs viewer menu
+### Changed
+- Updated translations for German, Spanish, French, Japanese, Portuguese, and Turkish
+- (Internal) Updated to Avalonia 11.0.9
+### Fixed
+- Fixed StableSwarmUI not installing properly on macOS
+- Fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS
+- Fixed [#335](https://github.com/LykosAI/StabilityMatrix/issues/335) Update hanging indefinitely after git step for Auto1111 and SDWebUI Forge
+- Fixed Inference output viewer menu "Copy" not copying image
+- Fixed image viewer dialog arrow key navigation not working
+- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins
+- Fixed unknown model types not showing on checkpoints page (thanks Jerry!)
+- Improved error handling for Inference Select Image hash calculation in case file is being written to while being read
+
+## v2.9.0-pre.2
+### Added
+- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`)
+- Added more Base Model search options to the CivitAI Model Browser
+- Added Stable Cascade to the HuggingFace Model Browser
+### Changed
+- (Internal) Updated to Avalonia 11.0.9
+### Fixed
+- Fixed image viewer dialog arrow key navigation not working
+- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins
+
+## v2.9.0-pre.1
+### Added
+- Added Inference Prompt Styles, with Prompt Expansion model support (i.e. Fooocus V2)
+- Added copy image support on linux and macOS for Inference outputs viewer menu
+### Fixed
+- Fixed StableSwarmUI not installing properly on macOS
+- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS
+- Fixed default command line args for SDWebUI Forge on macOS
+- Fixed output paths and output sharing for SDWebUI Forge
+- Maybe fixed update hanging for Auto1111 and SDWebUI Forge
+- Fixed Inference output viewer menu "Copy" not copying image
+
+## v2.9.0-dev.2
+### Added
+#### Inference
+- Added option to load a .yaml config file next to the model with the same name. Can be used with VPred and other models that require a config file.
+### Fixed
+- Fixed icon sizes of Inference Addons and Steps buttons
+
+## v2.9.0-dev.1
+### Added
+- Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI
+- Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel
+- Added extension management for SD.Next and Stable Diffusion WebUI-UX
+- Added the ability to choose where CivitAI model downloads are saved
+
+## v2.8.4
+### Fixed
+- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS
+
## v2.8.3
### Fixed
- Fixed user tokens read error causing failed downloads
diff --git a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
index e635ad2f..71efd8bb 100644
--- a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
+++ b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
@@ -19,8 +19,8 @@
-
-
+
+
diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml
index ad48795c..1f039a8a 100644
--- a/StabilityMatrix.Avalonia/App.axaml
+++ b/StabilityMatrix.Avalonia/App.axaml
@@ -63,6 +63,7 @@
+
@@ -77,6 +78,7 @@
+
-
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml
new file mode 100644
index 00000000..3332c1a5
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs
new file mode 100644
index 00000000..044410a2
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs
@@ -0,0 +1,52 @@
+using AsyncAwaitBestPractices;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using FluentAvalonia.UI.Controls;
+using StabilityMatrix.Avalonia.ViewModels.Inference;
+using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Models;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+[Transient]
+public class PromptExpansionCard : TemplatedControl
+{
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ var upscalerComboBox = e.NameScope.Find("PART_ModelComboBox") as FAComboBox;
+ upscalerComboBox!.SelectionChanged += UpscalerComboBox_OnSelectionChanged;
+ }
+
+ private void UpscalerComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (e.AddedItems.Count == 0)
+ return;
+
+ var item = e.AddedItems[0];
+ if (item is HybridModelFile { IsDownloadable: true })
+ {
+ // Reset the selection
+ e.Handled = true;
+
+ if (
+ e.RemovedItems.Count > 0
+ && e.RemovedItems[0] is HybridModelFile { IsDownloadable: false } removedItem
+ )
+ {
+ (sender as FAComboBox)!.SelectedItem = removedItem;
+ }
+ else
+ {
+ (sender as FAComboBox)!.SelectedItem = null;
+ }
+
+ // Show dialog to download the model
+ (DataContext as PromptExpansionCardViewModel)!
+ .RemoteDownloadCommand.ExecuteAsync(item)
+ .SafeFireAndForget();
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml
index e931a603..6c59723d 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml
@@ -39,7 +39,7 @@
Padding="12,4"
IsEnabled="{Binding !!Cards.Count}"
IsChecked="{Binding IsEditEnabled}">
-
+
diff --git a/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml
new file mode 100644
index 00000000..0e76024c
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml.cs b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml.cs
new file mode 100644
index 00000000..8e01e487
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml.cs
@@ -0,0 +1,135 @@
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using StabilityMatrix.Avalonia.Models.TreeFileExplorer;
+using StabilityMatrix.Core.Models.FileInterfaces;
+
+namespace StabilityMatrix.Avalonia.Controls;
+
+public class TreeFileExplorer : TemplatedControl
+{
+ public static readonly StyledProperty RootItemProperty =
+ AvaloniaProperty.Register("RootItem");
+
+ public TreeFileExplorerDirectory? RootItem
+ {
+ get => GetValue(RootItemProperty);
+ set => SetValue(RootItemProperty, value);
+ }
+
+ public static readonly StyledProperty RootPathProperty = AvaloniaProperty.Register<
+ TreeFileExplorer,
+ string?
+ >("RootPath");
+
+ public string? RootPath
+ {
+ get => GetValue(RootPathProperty);
+ set => SetValue(RootPathProperty, value);
+ }
+
+ public static readonly StyledProperty SelectedPathProperty = AvaloniaProperty.Register<
+ TreeFileExplorer,
+ IPathObject?
+ >("SelectedPath");
+
+ public IPathObject? SelectedPath
+ {
+ get => GetValue(SelectedPathProperty);
+ set => SetValue(SelectedPathProperty, value);
+ }
+
+ public static readonly StyledProperty CanSelectFilesProperty = AvaloniaProperty.Register<
+ TreeFileExplorer,
+ bool
+ >("CanSelectFiles", true);
+
+ public bool CanSelectFiles
+ {
+ get => GetValue(CanSelectFilesProperty);
+ set => SetValue(CanSelectFilesProperty, value);
+ }
+
+ public static readonly StyledProperty CanSelectFoldersProperty = AvaloniaProperty.Register<
+ TreeFileExplorer,
+ bool
+ >("CanSelectFolders", true);
+
+ public bool CanSelectFolders
+ {
+ get => GetValue(CanSelectFoldersProperty);
+ set => SetValue(CanSelectFoldersProperty, value);
+ }
+
+ public static readonly StyledProperty IndexFilesProperty = AvaloniaProperty.Register<
+ TreeFileExplorer,
+ bool
+ >("IndexFiles", true);
+
+ public bool IndexFiles
+ {
+ get => GetValue(IndexFilesProperty);
+ set => SetValue(IndexFilesProperty, value);
+ }
+
+ public static readonly StyledProperty IndexFoldersProperty = AvaloniaProperty.Register<
+ TreeFileExplorer,
+ bool
+ >("IndexFolders", true);
+
+ public bool IndexFolders
+ {
+ get => GetValue(IndexFoldersProperty);
+ set => SetValue(IndexFoldersProperty, value);
+ }
+
+ private TreeFileExplorerOptions GetOptions()
+ {
+ var options = TreeFileExplorerOptions.None;
+
+ if (CanSelectFiles)
+ {
+ options |= TreeFileExplorerOptions.CanSelectFiles;
+ }
+ if (CanSelectFolders)
+ {
+ options |= TreeFileExplorerOptions.CanSelectFolders;
+ }
+ if (IndexFiles)
+ {
+ options |= TreeFileExplorerOptions.IndexFiles;
+ }
+ if (IndexFolders)
+ {
+ options |= TreeFileExplorerOptions.IndexFolders;
+ }
+
+ return options;
+ }
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ if (RootItem is null)
+ {
+ RootItem = RootPath is null
+ ? null
+ : new TreeFileExplorerDirectory(new DirectoryPath(RootPath), GetOptions());
+ }
+ }
+
+ ///
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == RootPathProperty)
+ {
+ var path = change.GetNewValue();
+ RootItem = path is null
+ ? null
+ : new TreeFileExplorerDirectory(new DirectoryPath(path), GetOptions());
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
index 3f2476c9..77d5a384 100644
--- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs
+++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
@@ -192,7 +192,8 @@ public static class DesignData
null,
null,
null,
- null
+ null,
+ packageFactory
);
ObservableCacheEx.AddOrUpdate(
@@ -1007,6 +1008,8 @@ The gallery images are often inpainted, but you will get something very similar
public static ControlNetCardViewModel ControlNetCardViewModel =>
DialogFactory.Get();
+ public static string CurrentDirectory => Directory.GetCurrentDirectory();
+
public static Indexer Types { get; } = new();
public class Indexer
diff --git a/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs b/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs
index 258c2602..debbe0f3 100644
--- a/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs
+++ b/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs
@@ -27,6 +27,9 @@ public partial class MockInferenceClientManager : ObservableObject, IInferenceCl
public IObservableCollection ControlNetModels { get; } =
new ObservableCollectionExtended();
+ public IObservableCollection PromptExpansionModels { get; } =
+ new ObservableCollectionExtended();
+
public IObservableCollection Samplers { get; } =
new ObservableCollectionExtended(ComfySampler.Defaults);
@@ -64,28 +67,19 @@ public partial class MockInferenceClientManager : ObservableObject, IInferenceCl
}
///
- public Task CopyImageToInputAsync(
- FilePath imageFile,
- CancellationToken cancellationToken = default
- )
+ public Task CopyImageToInputAsync(FilePath imageFile, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
///
- public Task UploadInputImageAsync(
- ImageSource image,
- CancellationToken cancellationToken = default
- )
+ public Task UploadInputImageAsync(ImageSource image, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
///
- public Task WriteImageToInputAsync(
- ImageSource imageSource,
- CancellationToken cancellationToken = default
- )
+ public Task WriteImageToInputAsync(ImageSource imageSource, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
diff --git a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs
index d60c7725..0c0fd05e 100644
--- a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs
+++ b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs
@@ -16,4 +16,28 @@ public static class IOCommands
},
url => !string.IsNullOrWhiteSpace(url)
);
+
+ public static AsyncRelayCommand OpenFileBrowserCommand { get; } =
+ new(
+ async path =>
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ return;
+
+ await ProcessRunner.OpenFileBrowser(path);
+ },
+ path => !string.IsNullOrWhiteSpace(path)
+ );
+
+ public static AsyncRelayCommand OpenFolderBrowserCommand { get; } =
+ new(
+ async path =>
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ return;
+
+ await ProcessRunner.OpenFolderBrowser(path);
+ },
+ path => !string.IsNullOrWhiteSpace(path)
+ );
}
diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
index 296b1202..d31860db 100644
--- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
+++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
@@ -43,6 +45,18 @@ public class UnixPrerequisiteHelper(
private string NpmPath => Path.Combine(NodeDir, "bin", "npm");
private bool IsNodeInstalled => File.Exists(NpmPath);
+ private DirectoryPath DotnetDir => AssetsDir.JoinDir("dotnet");
+ private string DotnetPath => Path.Combine(DotnetDir, "dotnet");
+ private bool IsDotnetInstalled => File.Exists(DotnetPath);
+ private string Dotnet7DownloadUrlMacOs =>
+ "https://download.visualstudio.microsoft.com/download/pr/5bb0e0e4-2a8d-4aba-88ad-232e1f65c281/ee6d35f762d81965b4cf336edde1b318/dotnet-sdk-7.0.405-osx-arm64.tar.gz";
+ private string Dotnet8DownloadUrlMacOs =>
+ "https://download.visualstudio.microsoft.com/download/pr/ef083c06-7aee-4a4f-b18b-50c9a8990753/e206864e7910e81bbd9cb7e674ff1b4c/dotnet-sdk-8.0.101-osx-arm64.tar.gz";
+ private string Dotnet7DownloadUrlLinux =>
+ "https://download.visualstudio.microsoft.com/download/pr/5202b091-2406-445c-b40a-68a5b97c882b/b509f2a7a0eb61aea145b990b40b6d5b/dotnet-sdk-7.0.405-linux-x64.tar.gz";
+ private string Dotnet8DownloadUrlLinux =>
+ "https://download.visualstudio.microsoft.com/download/pr/9454f7dc-b98e-4a64-a96d-4eb08c7b6e66/da76f9c6bc4276332b587b771243ae34/dotnet-sdk-8.0.101-linux-x64.tar.gz";
+
// Cached store of whether or not git is installed
private bool? isGitInstalled;
@@ -78,6 +92,28 @@ public class UnixPrerequisiteHelper(
{
await InstallNodeIfNecessary(progress);
}
+
+ if (prerequisites.Contains(PackagePrerequisite.Dotnet))
+ {
+ await InstallDotnetIfNecessary(progress);
+ }
+ }
+
+ public async Task InstallDotnetIfNecessary(IProgress? progress = null)
+ {
+ if (IsDotnetInstalled)
+ return;
+
+ if (Compat.IsMacOS)
+ {
+ await DownloadAndExtractPrerequisite(progress, Dotnet7DownloadUrlMacOs, DotnetDir);
+ await DownloadAndExtractPrerequisite(progress, Dotnet8DownloadUrlMacOs, DotnetDir);
+ }
+ else
+ {
+ await DownloadAndExtractPrerequisite(progress, Dotnet7DownloadUrlLinux, DotnetDir);
+ await DownloadAndExtractPrerequisite(progress, Dotnet8DownloadUrlLinux, DotnetDir);
+ }
}
private async Task InstallVirtualenvIfNecessary(IProgress? progress = null)
@@ -276,6 +312,7 @@ public class UnixPrerequisiteHelper(
return ProcessRunner.RunBashCommand(args.Prepend("git").ToArray(), workingDirectory ?? "");
}
+ [Localizable(false)]
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("macOS")]
public async Task RunNpm(
@@ -285,27 +322,73 @@ public class UnixPrerequisiteHelper(
IReadOnlyDictionary? envVars = null
)
{
- var command = args.Prepend([NpmPath]);
+ var process = ProcessRunner.StartAnsiProcess(
+ NpmPath,
+ args,
+ workingDirectory,
+ onProcessOutput,
+ envVars
+ );
- var result = await ProcessRunner.RunBashCommand(command.ToArray(), workingDirectory ?? "", envVars);
- if (result.ExitCode != 0)
- {
- Logger.Error(
- "npm command [{Command}] failed with exit code " + "{ExitCode}:\n{StdOut}\n{StdErr}",
- command,
- result.ExitCode,
- result.StandardOutput,
- result.StandardError
- );
+ await process.WaitForExitAsync();
- throw new ProcessException(
- $"npm command [{command}] failed with exit code"
- + $" {result.ExitCode}:\n{result.StandardOutput}\n{result.StandardError}"
- );
- }
+ if (process.ExitCode == 0)
+ return;
- onProcessOutput?.Invoke(ProcessOutput.FromStdOutLine(result.StandardOutput));
- onProcessOutput?.Invoke(ProcessOutput.FromStdErrLine(result.StandardError));
+ var stdOut = await process.StandardOutput.ReadToEndAsync();
+ var stdErr = await process.StandardError.ReadToEndAsync();
+
+ Logger.Error(
+ "RunNpm with args [{Args}] failed with exit code " + "{ExitCode}:\n{StdOut}\n{StdErr}",
+ args,
+ process.ExitCode,
+ stdOut,
+ stdErr
+ );
+
+ throw new ProcessException(
+ $"RunNpm with args [{args}] failed with exit code" + $" {process.ExitCode}:\n{stdOut}\n{stdErr}"
+ );
+ }
+
+ [SupportedOSPlatform("Linux")]
+ [SupportedOSPlatform("macOS")]
+ public async Task RunDotnet(
+ ProcessArgs args,
+ string? workingDirectory = null,
+ Action? onProcessOutput = null,
+ IReadOnlyDictionary? envVars = null,
+ bool waitForExit = true
+ )
+ {
+ var process = ProcessRunner.StartAnsiProcess(
+ DotnetPath,
+ args,
+ workingDirectory,
+ onProcessOutput,
+ envVars
+ );
+
+ if (!waitForExit)
+ return process;
+
+ await process.WaitForExitAsync();
+
+ if (process.ExitCode == 0)
+ return process;
+
+ Logger.Error(
+ "dotnet8 with args [{Args}] failed with exit code " + "{ExitCode}:\n{StdOut}\n{StdErr}",
+ args,
+ process.ExitCode,
+ process.StandardOutput,
+ process.StandardError
+ );
+
+ throw new ProcessException(
+ $"dotnet8 with args [{args}] failed with exit code"
+ + $" {process.ExitCode}:\n{process.StandardOutput}\n{process.StandardError}"
+ );
}
[SupportedOSPlatform("Linux")]
@@ -353,6 +436,40 @@ public class UnixPrerequisiteHelper(
File.Delete(nodeDownloadPath);
}
+ private async Task DownloadAndExtractPrerequisite(
+ IProgress? progress,
+ string downloadUrl,
+ string extractPath
+ )
+ {
+ Logger.Info($"Downloading {downloadUrl}");
+
+ var downloadPath = AssetsDir.JoinFile(Path.GetFileName(downloadUrl));
+
+ await downloadService.DownloadToFileAsync(downloadUrl, downloadPath, progress: progress);
+
+ Logger.Info("Installing prereq");
+ progress?.Report(
+ new ProgressReport(
+ progress: 0.5f,
+ isIndeterminate: true,
+ type: ProgressType.Generic,
+ message: "Installing prerequisites..."
+ )
+ );
+
+ Directory.CreateDirectory(extractPath);
+
+ // unzip
+ await ArchiveHelper.Extract7ZAuto(downloadPath, extractPath);
+
+ progress?.Report(
+ new ProgressReport(progress: 1f, message: "Node install complete", type: ProgressType.Generic)
+ );
+
+ File.Delete(downloadPath);
+ }
+
[UnsupportedOSPlatform("Linux")]
[UnsupportedOSPlatform("macOS")]
public Task InstallTkinterIfNecessary(IProgress? progress = null)
diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
index 18c33143..d0b5b2cc 100644
--- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
+++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
@@ -7,7 +8,6 @@ using System.Threading.Tasks;
using Microsoft.Win32;
using NLog;
using Octokit;
-using PropertyModels.Extensions;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
@@ -35,6 +35,12 @@ public class WindowsPrerequisiteHelper(
"https://cdn.lykos.ai/tkinter-cpython-embedded-3.10.11-win-x64.zip";
private const string NodeDownloadUrl = "https://nodejs.org/dist/v20.11.0/node-v20.11.0-win-x64.zip";
+ private const string Dotnet7DownloadUrl =
+ "https://download.visualstudio.microsoft.com/download/pr/2133b143-9c4f-4daa-99b0-34fa6035d67b/193ede446d922eb833f1bfe0239be3fc/dotnet-sdk-7.0.405-win-x64.zip";
+
+ private const string Dotnet8DownloadUrl =
+ "https://download.visualstudio.microsoft.com/download/pr/6902745c-34bd-4d66-8e84-d5b61a17dfb7/e61732b00f7e144e162d7e6914291f16/dotnet-sdk-8.0.101-win-x64.zip";
+
private string HomeDir => settingsManager.LibraryDir;
private string VcRedistDownloadPath => Path.Combine(HomeDir, "vcredist.x64.exe");
@@ -59,6 +65,10 @@ public class WindowsPrerequisiteHelper(
private string TkinterExistsPath => Path.Combine(PythonDir, "tkinter");
private string NodeExistsPath => Path.Combine(AssetsDir, "nodejs", "npm.cmd");
private string NodeDownloadPath => Path.Combine(AssetsDir, "nodejs.zip");
+ private string Dotnet7DownloadPath => Path.Combine(AssetsDir, "dotnet-sdk-7.0.405-win-x64.zip");
+ private string Dotnet8DownloadPath => Path.Combine(AssetsDir, "dotnet-sdk-8.0.101-win-x64.zip");
+ private string DotnetExtractPath => Path.Combine(AssetsDir, "dotnet");
+ private string DotnetExistsPath => Path.Combine(DotnetExtractPath, "dotnet.exe");
public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin");
public bool IsPythonInstalled => File.Exists(PythonDllPath);
@@ -155,6 +165,11 @@ public class WindowsPrerequisiteHelper(
await InstallNodeIfNecessary(progress);
}
+ if (prerequisites.Contains(PackagePrerequisite.Dotnet))
+ {
+ await InstallDotnetIfNecessary(progress);
+ }
+
if (prerequisites.Contains(PackagePrerequisite.Tkinter))
{
await InstallTkinterIfNecessary(progress);
@@ -425,15 +440,86 @@ public class WindowsPrerequisiteHelper(
public async Task InstallNodeIfNecessary(IProgress? progress = null)
{
if (File.Exists(NodeExistsPath))
- {
- Logger.Info("node already installed");
return;
+
+ await DownloadAndExtractPrerequisite(progress, NodeDownloadUrl, NodeDownloadPath, AssetsDir);
+
+ var extractedNodeDir = Path.Combine(AssetsDir, "node-v20.11.0-win-x64");
+ if (Directory.Exists(extractedNodeDir))
+ {
+ Directory.Move(extractedNodeDir, Path.Combine(AssetsDir, "nodejs"));
}
+ }
+
+ [SupportedOSPlatform("windows")]
+ public async Task InstallDotnetIfNecessary(IProgress? progress = null)
+ {
+ if (File.Exists(DotnetExistsPath))
+ return;
+
+ await DownloadAndExtractPrerequisite(
+ progress,
+ Dotnet7DownloadUrl,
+ Dotnet7DownloadPath,
+ DotnetExtractPath
+ );
+ await DownloadAndExtractPrerequisite(
+ progress,
+ Dotnet8DownloadUrl,
+ Dotnet8DownloadPath,
+ DotnetExtractPath
+ );
+ }
+
+ public async Task RunDotnet(
+ ProcessArgs args,
+ string? workingDirectory = null,
+ Action? onProcessOutput = null,
+ IReadOnlyDictionary? envVars = null,
+ bool waitForExit = true
+ )
+ {
+ var process = ProcessRunner.StartAnsiProcess(
+ DotnetExistsPath,
+ args,
+ workingDirectory,
+ onProcessOutput,
+ envVars
+ );
+
+ if (!waitForExit)
+ return process;
- Logger.Info("Downloading node");
- await downloadService.DownloadToFileAsync(NodeDownloadUrl, NodeDownloadPath, progress: progress);
+ await process.WaitForExitAsync();
+
+ if (process.ExitCode == 0)
+ return process;
+
+ Logger.Error(
+ "dotnet8 with args [{Args}] failed with exit code " + "{ExitCode}:\n{StdOut}\n{StdErr}",
+ args,
+ process.ExitCode,
+ process.StandardOutput,
+ process.StandardError
+ );
- Logger.Info("Installing node");
+ throw new ProcessException(
+ $"dotnet8 with args [{args}] failed with exit code"
+ + $" {process.ExitCode}:\n{process.StandardOutput}\n{process.StandardError}"
+ );
+ }
+
+ private async Task DownloadAndExtractPrerequisite(
+ IProgress? progress,
+ string downloadUrl,
+ string downloadPath,
+ string extractPath
+ )
+ {
+ Logger.Info($"Downloading {downloadUrl} to {downloadPath}");
+ await downloadService.DownloadToFileAsync(downloadUrl, downloadPath, progress: progress);
+
+ Logger.Info("Extracting prerequisite");
progress?.Report(
new ProgressReport(
progress: 0.5f,
@@ -443,18 +529,20 @@ public class WindowsPrerequisiteHelper(
)
);
- // unzip
- await ArchiveHelper.Extract(NodeDownloadPath, AssetsDir, progress);
+ Directory.CreateDirectory(extractPath);
- // move to assets dir
- var existingNodeDir = Path.Combine(AssetsDir, "node-v20.11.0-win-x64");
- Directory.Move(existingNodeDir, Path.Combine(AssetsDir, "nodejs"));
+ // unzip
+ await ArchiveHelper.Extract(downloadPath, extractPath, progress);
progress?.Report(
- new ProgressReport(progress: 1f, message: "Node install complete", type: ProgressType.Generic)
+ new ProgressReport(
+ progress: 1f,
+ message: "Prerequisite install complete",
+ type: ProgressType.Generic
+ )
);
- File.Delete(NodeDownloadPath);
+ File.Delete(downloadPath);
}
private async Task UnzipGit(IProgress? progress = null)
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
index 2ddf7c05..0538cae2 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
+++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
@@ -644,6 +644,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Please select a download location..
+ ///
+ public static string Error_PleaseSelectDownloadLocation {
+ get {
+ return ResourceManager.GetString("Error_PleaseSelectDownloadLocation", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to About.
///
@@ -887,6 +896,24 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to ComfyUI is required to install this package. Would you like to install it now?.
+ ///
+ public static string Label_ComfyRequiredDetail {
+ get {
+ return ResourceManager.GetString("Label_ComfyRequiredDetail", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ComfyUI Required.
+ ///
+ public static string Label_ComfyRequiredTitle {
+ get {
+ return ResourceManager.GetString("Label_ComfyRequiredTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Comments.
///
@@ -914,6 +941,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Config.
+ ///
+ public static string Label_Config {
+ get {
+ return ResourceManager.GetString("Label_Config", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Confirm Delete.
///
@@ -1653,7 +1689,7 @@ namespace StabilityMatrix.Avalonia.Languages {
}
///
- /// Looks up a localized string similar to .
+ /// Looks up a localized string similar to None.
///
public static string Label_NotificationOption_None {
get {
@@ -2021,6 +2057,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Select Download Location:.
+ ///
+ public static string Label_SelectDownloadLocation {
+ get {
+ return ResourceManager.GetString("Label_SelectDownloadLocation", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Select new Data Directory.
///
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.de.resx b/StabilityMatrix.Avalonia/Languages/Resources.de.resx
index 1821cda1..1a2ef15f 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.de.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.de.resx
@@ -521,6 +521,7 @@
Wählen Sie Ihre bevorzugte Schnittstelle und klicken Sie auf Installieren, um loszulegen.
+ Fuzzy
Installiert
@@ -777,6 +778,9 @@
API Schlüssel
+
+ Accounts
+
Preprocessor
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.es.resx b/StabilityMatrix.Avalonia/Languages/Resources.es.resx
index 4dccbf2a..5efb7a15 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.es.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.es.resx
@@ -522,6 +522,7 @@
Elige tu interfaz preferida y pulsa Instalar para comenzar
+ Fuzzy
Instalando
@@ -782,4 +783,184 @@
Cuentas
+
+ Preprocesador
+
+
+ Fortaleza
+
+
+ Control de Peso
+
+
+ Control de Pasos
+
+
+ Debes iniciar sesión para descargar este checkpoint. Ingresa una clave API de CivitAI en la configuración.
+
+
+ Descarga Fallida
+
+
+ Actualizaciones automáticas
+
+
+ Para los usuarios primerizos. Las versiones preliminares serán más fiables que las del canal de desarrollo y estarán disponibles antes de las versiones estables. Tus comentarios nos ayudarán enormemente a descubrir problemas y pulir elementos de diseño.
+
+
+ Para usuarios técnicos. Sé el primero en acceder a nuestras compilaciones de desarrollo desde las ramas de funciones tan pronto como estén disponibles. Es posible que haya algunas asperezas y errores a medida que experimentamos con nuevas funciones.
+
+
+ Actualizaciones
+
+
+ Estás al día
+
+
+ Última comprobación: {0}
+
+
+ Copiar palabras desencadenantes
+
+
+ Palabras desencadenantes:
+
+
+ Aquí se pueden habilitar carpetas adicionales como IPAdapters y TextualInversions (embeddings)
+
+
+ Abrir en Hugging Face
+
+
+ Actualizar metadatos existentes
+
+
+ General
+ A general settings category
+
+
+ Inferencia
+ The Inference feature page
+
+
+ Prompt
+ A settings category for Inference generation prompts
+
+
+ Archivos de imágenes resultantes
+
+
+ Visor de Imágenes
+
+
+ Auto Completado
+
+
+ Reemplazar guiones bajos con espacios al insertar terminaciones
+
+
+ Etiquetas de Prompt
+ Tags for image generation prompts
+
+
+ Importar Etiquetas de Prompt
+
+
+ Archivo de etiquetas que se utilizarán para sugerir terminaciones (admite el formato .csv a1111-sd-webui-tagcomplete)
+
+
+ Información del Sistema
+
+
+ CivitAI
+
+
+ Hugging Face
+
+
+ Complementos
+ Inference Sampler Addons
+
+
+ Guardar Imagen Intermedia
+ Inference module step to save an intermediate image
+
+
+ Ajustes
+
+
+ Seleccionar Archivo
+
+
+ Reemplazar contenido
+
+
+ No disponible aún
+
+
+ La función estará disponible en una actualización futura
+
+
+ Archivo de Imagen Faltante
+
+
+ Modo Vacaciones
+
+
+ Saltar CLIP
+
+
+ Imagen a Vídeo
+
+
+ Frames por Segundo
+
+
+ CFG Mínimo
+
+
+ Sin pérdidas
+
+
+ Frames
+
+
+ Motion Bucket ID
+
+
+ Nivel de aumento
+
+
+ Método
+
+
+ Calidad
+
+
+ Buscar en el navegador de modelos
+
+
+ Instalado
+
+
+ No se encuentran extensiones.
+
+
+ Ocultar
+
+
+ Copiar detalles
+
+
+ Descargar
+
+
+ Verifica el progreso de instalación de tus paquetes y descargas de modelos aquí.
+
+
+ Modelos recomendados
+
+
+ Mientras se instala tu paquete, aquí hay algunos modelos que recomendamos para ayudarte a comenzar.
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx b/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
index b8eee78d..6e195f4d 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
@@ -319,7 +319,7 @@
Recherche
- Trier
+ Tri
Période
@@ -810,6 +810,9 @@
Dernière vérification: {0}
+
+ Les dossiers additionnels comme IPAdapters ou TextualInversions (embeddings) peuvent être activés ici
+
Ouvrir sur Hugging Face
@@ -834,6 +837,9 @@
Auto-complétion
+
+ Remplacer les underscores par des espaces lors de l'auto-complétion.
+
Informations système
@@ -888,10 +894,40 @@
Motion Bucket ID
+
+ Niveau d'augmentation
+
Méthode
Qualité
+
+ Trouver dans l'explorateur de modèle
+
+
+ Installé
+
+
+ Pas d'extensions trouvés.
+
+
+ Masquer
+
+
+ Copier les détails
+
+
+ Télécharger
+
+
+ Vérifiez la progression de l'installation de votre paquet ainsi que le téléchargement de votre modèle ici.
+
+
+ Modèles recommandés
+
+
+ Pendant que votre paquet s'installe, voici quelques modèles que nous recommandons pour vous aidez à démarrer.
+
\ 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 9449fc9f..3187678c 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx
@@ -530,6 +530,7 @@
好きなUIをインストールして始めよう
+ Fuzzy
インストール中
@@ -706,6 +707,24 @@
更新
+
+ GitHubで開く
+
+
+ ユーザ名
+
+
+ パスワード
+
+
+ ログイン
+
+
+ サインアップ
+
+
+ パスワード再確認
+
ダウンロードにはCivitAIのログインが必要です。SettingからAPIキーを入力してください。
@@ -715,6 +734,9 @@
Devビルドはテクニカルなユーザ向けです。新機能をいち早く利用できます。荒削りな部分やバグがあるかもしれません。
+
+ IPAdaptersやTextualInversions(embeddings)などのフォルダは、ここで有効にできます。
+
Hugging Faceで開く
@@ -734,4 +756,13 @@
非圧縮
+
+ Packageのインストール・Modelのダウンロードの進捗状況はこちらで確認できます。
+
+
+ 推奨Model
+
+
+ Packageのインストール中に、推奨Modelをいくつかご紹介しましょう。
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx b/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx
index 9c208fff..c74bd016 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx
@@ -196,7 +196,7 @@
Passos - Refinamento
- Escale de CFG
+ Escala de CFG
Nível de Denoising
@@ -391,7 +391,7 @@
Pasta de Modelos
- Catagorias
+ Categorias
Vamos começar!
@@ -409,7 +409,7 @@
Exibir imagens dos Modelos
- Aparencia
+ Aparência
Tema
@@ -521,6 +521,7 @@
Escolha sua interface preferida e clique em Instalar para começar
+ Fuzzy
Instalando
@@ -840,7 +841,7 @@
The Inference feature page
- Incitar
+ Prompt
A settings category for Inference generation prompts
@@ -853,17 +854,17 @@
Preenchimento automático
- Substitua sublinhados por espaços ao inserir conclusões
+ Substitua caracteres sublinhados por espaços ao inserir conclusões
- Etiquetas de prompt
+ Etiquetas de Prompt
Tags for image generation prompts
- Tags de prompt de importação
+ Importar Tags de Prompt
- Arquivo de tags a ser usado para sugerir conclusões (suporta o formato .csv a1111-sd-webui-tagcomplete)
+ Arquivo de tags a ser usado para Sugestão de Completions (suporta o formato .csv a1111-sd-webui-tagcomplete)
Informação do sistema
@@ -872,7 +873,7 @@
CivitAI
- Abraçando o rosto
+ Hugging Face
Complementos
@@ -901,7 +902,7 @@
Arquivo de imagem não encontrado
- Modo Férias
+ Modo Feriado
Pular CLIPE
@@ -930,4 +931,7 @@
Método
+
+ Qualidade
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx
index 4b038c76..4f15299e 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.resx
@@ -946,7 +946,7 @@
Notifications
-
+ None
Download
@@ -960,4 +960,19 @@
While your package is installing, here are some models we recommend to help you get started.
+
+ ComfyUI Required
+
+
+ ComfyUI is required to install this package. Would you like to install it now?
+
+
+ Please select a download location.
+
+
+ Select Download Location:
+
+
+ Config
+
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx b/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
index 603f6feb..8d2e1d7f 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
@@ -133,7 +133,7 @@
Dil
- Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekiyor
+ Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekir.
Yeniden başlat
@@ -166,7 +166,7 @@
Dallar
- İçe aktarmak için chekpoints'leri buraya sürükleyip bırakın
+ İçe aktarmak için chekpoints'leri buraya sürükleyip bırakın.
Vurgu
@@ -235,7 +235,7 @@
Sponsor Ol
- Discord Sunucusuna Katıl
+ Discord Sunucumuza Katıl
İndirmeler
@@ -313,7 +313,7 @@
Tüm Sürümler
- Modelleri, #etiketleri veya @kullanıcıları ara
+ Modelleri,Tagları (#tag) veya Kullanıcıları (@user) Buradan Arayabilirsin
Ara
@@ -322,13 +322,13 @@
Sırala
- Süre
+ Sıralama Tarihi
Model Türü
- Temel Model
+ Model Altyapısı
NSFW İçerik Göster
@@ -376,10 +376,10 @@
Klasör
- İçe aktarma için dosyayı buraya bırakın
+ Dosyayı içeri aktarmak için lütfen sürükleyip işaretlenen alana bırakın.
- Metadata ile içeri aktar
+ Dosyaları metadata ile içeri aktar
Yeni yerel içe aktarmalar için bağlı meta veri arayın
@@ -448,7 +448,7 @@
Entegrasyonlar
- Discord Zengin Varlık
+ Discord Etkinliğini StabilityMatrix Olarak Göster.
Sistem
@@ -469,10 +469,10 @@
Tüm Kullanıcılar İçin Ekle
- Yeni Veri Dizini Seç
+ Yeni Dosya Dizinini Seç
- Mevcut veriyi taşımaz
+ Bu işlem mevcut veriyi yeni dizine taşımaz.
Dizin Seçin
@@ -490,7 +490,7 @@
Başlamak için Başlat'a tıklayın!
- Dur
+ Durdur
Giriş Gönder
@@ -521,6 +521,7 @@
Tercih ettiğiniz arayüzü seçin ve başlamak için Yükle'ye tıklayın
+ Fuzzy
Yükleniyor
@@ -694,10 +695,10 @@
{0} resim seçildi
- Çıkış Klasörü
+ Çıktı Klasörü
- Çıkış Türü
+ Çıktı Türü
Seçimi Temizle
@@ -721,7 +722,7 @@
Upscale
- Çıkış Tarayıcısı
+ Medya Kitaplığı
1 resim seçildi
@@ -933,4 +934,31 @@
Kalite
+
+ Model Tarayıcıda Bul
+
+
+ Kurulmuş
+
+
+ Uzantı bulunamadı.
+
+
+ Gizle
+
+
+ Ayrıntıları Kopyala
+
+
+ İndir
+
+
+ Paket kurulumlarınızın ve model indirmelerinizin ilerlemesini buradan kontrol edin.
+
+
+ Önerilen Modeller
+
+
+ Paketiniz yüklenirken başlamanıza yardımcı olması için önerdiğimiz bazı modeller aşağıda verilmiştir.
+
\ No newline at end of file
diff --git a/StabilityMatrix.Avalonia/Models/AppArgs.cs b/StabilityMatrix.Avalonia/Models/AppArgs.cs
index b1e72bf9..7f836b11 100644
--- a/StabilityMatrix.Avalonia/Models/AppArgs.cs
+++ b/StabilityMatrix.Avalonia/Models/AppArgs.cs
@@ -77,6 +77,13 @@ public class AppArgs
[Option("data-dir", HelpText = "Override data directory")]
public string? DataDirectoryOverride { get; set; }
+ ///
+ /// Launch an installed package on startup
+ /// Can use package ID or name
+ ///
+ [Option("launch-package", HelpText = "Package ID or name to launch on startup")]
+ public string? LaunchPackageName { get; set; }
+
///
/// Custom Uri protocol handler
/// This will send the Uri to the running instance of the app via IPC and exit
diff --git a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
index 8119abc1..375ca17f 100644
--- a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
+++ b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
-using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
+using System.IO;
+using System.IO.Hashing;
+using System.Text;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
+using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
namespace StabilityMatrix.Avalonia.Models.Inference;
@@ -17,19 +20,40 @@ public class ModuleApplyStepEventArgs : EventArgs
public ModuleApplyStepTemporaryArgs Temp { get; } = new();
///
- /// Index of the step in the pipeline.
+ /// Generation overrides (like hires fix generate, current seed generate, etc.)
///
- public int StepIndex { get; init; }
+ public IReadOnlyDictionary IsEnabledOverrides { get; init; } = new Dictionary();
- ///
- /// Index
- ///
- public int StepTypeIndex { get; init; }
+ public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = [];
+
+ public void AddFileTransfer(string sourcePath, string destinationRelativePath)
+ {
+ FilesToTransfer.Add((sourcePath, destinationRelativePath));
+ }
///
- /// Generation overrides (like hires fix generate, current seed generate, etc.)
+ /// Adds a file transfer to `models/configs`
///
- public IReadOnlyDictionary IsEnabledOverrides { get; init; } = new Dictionary();
+ /// The destination relative path
+ public string AddFileTransferToConfigs(string sourcePath)
+ {
+ // To avoid conflicts, we'll add the file name's crc32 before the extension
+ var sourceNameWithoutExtension = Path.GetFileNameWithoutExtension(sourcePath);
+ var sourceExtension = Path.GetExtension(sourcePath);
+ var sourceNameCrc = Crc32.Hash(Encoding.UTF8.GetBytes(sourceNameWithoutExtension));
+ var sourceNameCrcShort = BitConverter
+ .ToString(sourceNameCrc)
+ .ToLowerInvariant()
+ .Replace("-", string.Empty)[..4];
+
+ var destFileName = $"{sourceNameWithoutExtension}_{sourceNameCrcShort}{sourceExtension}";
+
+ var destPath = Path.Combine("models", "configs", destFileName);
+
+ FilesToTransfer.Add((sourcePath, destPath));
+
+ return destPath;
+ }
public class ModuleApplyStepTemporaryArgs
{
diff --git a/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs b/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs
index 22029c30..cc088a94 100644
--- a/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs
+++ b/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs
@@ -1,10 +1,10 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Nodes;
namespace StabilityMatrix.Avalonia.Models.Inference;
-[JsonSerializable(typeof(PromptCardModel))]
public class PromptCardModel
{
- public string? Prompt { get; set; }
- public string? NegativePrompt { get; set; }
+ public string? Prompt { get; init; }
+ public string? NegativePrompt { get; init; }
+ public JsonObject? ModulesCardState { get; init; }
}
diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerDirectory.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerDirectory.cs
new file mode 100644
index 00000000..f5c19ed0
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerDirectory.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using StabilityMatrix.Core.Models.FileInterfaces;
+
+namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;
+
+public class TreeFileExplorerDirectory(IPathObject path, TreeFileExplorerOptions options)
+ : TreeFileExplorerItem(path, options)
+{
+ public IEnumerable Children =>
+ GetChildren(Path, Options)
+ .OrderByDescending(item => item.Path is DirectoryPath)
+ .ThenBy(item => item.Path.Name);
+
+ private static IEnumerable GetChildren(
+ IPathObject pathObject,
+ TreeFileExplorerOptions options
+ )
+ {
+ return pathObject switch
+ {
+ FilePath => Enumerable.Empty(),
+ DirectoryPath directoryPath => GetChildren(directoryPath, options),
+ _ => throw new NotSupportedException()
+ };
+ }
+
+ private static IEnumerable GetChildren(
+ DirectoryPath directoryPath,
+ TreeFileExplorerOptions options
+ )
+ {
+ if (options.HasFlag(TreeFileExplorerOptions.IndexFiles))
+ {
+ foreach (var file in directoryPath.EnumerateFiles())
+ {
+ yield return new TreeFileExplorerFile(file, options);
+ }
+ }
+
+ if (options.HasFlag(TreeFileExplorerOptions.IndexFolders))
+ {
+ foreach (var directory in directoryPath.EnumerateDirectories())
+ {
+ yield return new TreeFileExplorerDirectory(directory, options);
+ }
+ }
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerFile.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerFile.cs
new file mode 100644
index 00000000..db793f67
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerFile.cs
@@ -0,0 +1,6 @@
+using StabilityMatrix.Core.Models.FileInterfaces;
+
+namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;
+
+public class TreeFileExplorerFile(IPathObject path, TreeFileExplorerOptions options)
+ : TreeFileExplorerItem(path, options);
diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerItem.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerItem.cs
new file mode 100644
index 00000000..c0b49dc4
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerItem.cs
@@ -0,0 +1,10 @@
+using StabilityMatrix.Core.Models.FileInterfaces;
+
+namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;
+
+public class TreeFileExplorerItem(IPathObject path, TreeFileExplorerOptions options)
+{
+ public IPathObject Path { get; } = path;
+
+ public TreeFileExplorerOptions Options { get; } = options;
+}
diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerOptions.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerOptions.cs
new file mode 100644
index 00000000..1afd274d
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerOptions.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;
+
+[Flags]
+public enum TreeFileExplorerOptions
+{
+ None = 0,
+
+ IndexFiles = 1 << 5,
+ IndexFolders = 1 << 6,
+
+ CanSelectFiles = 1 << 10,
+ CanSelectFolders = 1 << 11,
+}
diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerType.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerType.cs
new file mode 100644
index 00000000..801d18e8
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerType.cs
@@ -0,0 +1,8 @@
+namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;
+
+public enum TreeFileExplorerType
+{
+ None,
+ File,
+ Directory
+}
diff --git a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs
index b0d8fb13..fc134c74 100644
--- a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs
+++ b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs
@@ -12,10 +12,7 @@ using StabilityMatrix.Core.Models.FileInterfaces;
namespace StabilityMatrix.Avalonia.Services;
-public interface IInferenceClientManager
- : IDisposable,
- INotifyPropertyChanged,
- INotifyPropertyChanging
+public interface IInferenceClientManager : IDisposable, INotifyPropertyChanged, INotifyPropertyChanging
{
ComfyClient? Client { get; set; }
@@ -43,6 +40,7 @@ public interface IInferenceClientManager
IObservableCollection Models { get; }
IObservableCollection VaeModels { get; }
IObservableCollection ControlNetModels { get; }
+ IObservableCollection PromptExpansionModels { get; }
IObservableCollection Samplers { get; }
IObservableCollection Upscalers { get; }
IObservableCollection Schedulers { get; }
@@ -51,10 +49,7 @@ public interface IInferenceClientManager
Task UploadInputImageAsync(ImageSource image, CancellationToken cancellationToken = default);
- Task WriteImageToInputAsync(
- ImageSource imageSource,
- CancellationToken cancellationToken = default
- );
+ Task WriteImageToInputAsync(ImageSource imageSource, CancellationToken cancellationToken = default);
Task ConnectAsync(CancellationToken cancellationToken = default);
diff --git a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
index b3afcdf2..5ad092af 100644
--- a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
+++ b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs
@@ -74,6 +74,14 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
public IObservableCollection ControlNetModels { get; } =
new ObservableCollectionExtended();
+ private readonly SourceCache promptExpansionModelsSource = new(p => p.GetId());
+
+ private readonly SourceCache downloadablePromptExpansionModelsSource =
+ new(p => p.GetId());
+
+ public IObservableCollection PromptExpansionModels { get; } =
+ new ObservableCollectionExtended();
+
private readonly SourceCache samplersSource = new(p => p.Name);
public IObservableCollection Samplers { get; } =
@@ -130,6 +138,18 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
.Bind(ControlNetModels)
.Subscribe();
+ promptExpansionModelsSource
+ .Connect()
+ .Or(downloadablePromptExpansionModelsSource.Connect())
+ .Sort(
+ SortExpressionComparer
+ .Ascending(f => f.Type)
+ .ThenByAscending(f => f.ShortDisplayName)
+ )
+ .DeferUntilLoaded()
+ .Bind(PromptExpansionModels)
+ .Subscribe();
+
vaeModelsDefaults.AddOrUpdate(HybridModelFile.Default);
vaeModelsDefaults.Connect().Or(vaeModelsSource.Connect()).Bind(VaeModels).Subscribe();
@@ -199,6 +219,8 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
);
}
+ // Prompt Expansion indexing is local only
+
// Fetch sampler names from KSampler node
if (await Client.GetSamplerNamesAsync() is { } samplerNames)
{
@@ -277,6 +299,22 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
);
downloadableControlNetModelsSource.EditDiff(downloadableControlNets, HybridModelFile.Comparer);
+ // Load local prompt expansion models
+ promptExpansionModelsSource.EditDiff(
+ modelIndexService
+ .GetFromModelIndex(SharedFolderType.PromptExpansion)
+ .Select(HybridModelFile.FromLocal),
+ HybridModelFile.Comparer
+ );
+
+ // Downloadable PromptExpansion models
+ downloadablePromptExpansionModelsSource.EditDiff(
+ RemoteModels.PromptExpansionModels.Where(
+ u => !promptExpansionModelsSource.Lookup(u.GetId()).HasValue
+ ),
+ HybridModelFile.Comparer
+ );
+
// Load local VAE models
vaeModelsSource.EditDiff(
modelIndexService.GetFromModelIndex(SharedFolderType.VAE).Select(HybridModelFile.FromLocal),
@@ -369,6 +407,7 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
inferenceInputs.Create();
}
+ [MemberNotNull(nameof(Client))]
private async Task ConnectAsyncImpl(Uri uri, CancellationToken cancellationToken = default)
{
if (IsConnected)
@@ -480,11 +519,8 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient
await ConnectAsyncImpl(uri, cancellationToken);
- var packageDir = new DirectoryPath(packagePair.InstalledPackage.FullPath);
-
- // Set package paths
- Client!.OutputImagesDir = packageDir.JoinDir("output");
- Client!.InputImagesDir = packageDir.JoinDir("input");
+ Client.LocalServerPackage = packagePair;
+ Client.LocalServerPath = packagePair.InstalledPackage.FullPath!;
}
public async Task CloseAsync()
diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
index 1190f686..4929efc4 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
@@ -17,7 +17,7 @@
app.manifest
true
./Assets/Icon.ico
- 2.9.0-dev.999
+ 2.10.0-dev.999
$(Version)
true
true
@@ -39,13 +39,13 @@
-
+
-
-
-
+
+
+
-
+
@@ -65,8 +65,8 @@
-
-
+
+
@@ -77,23 +77,26 @@
-
+
-
+
+
-
+
-
-
+
+
-
-
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings
index 78669f79..ef254925 100644
--- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings
+++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings
@@ -1,3 +1,5 @@
+ Yes
+ Pessimistic
UI
True
diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
index 7565401b..61d77d82 100644
--- a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
+++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml
@@ -10,7 +10,10 @@
-
+
-
+
-
+
+
+
+
+
+
@@ -236,7 +261,7 @@
-
+