Browse Source

Merge branch 'dev' into downmerge

pull/495/head
JT 9 months ago committed by GitHub
parent
commit
a4040d16a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  2. 109
      StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
  3. 112
      StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
  4. 18
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  5. 6
      StabilityMatrix.Avalonia/Languages/Resources.resx
  6. 6
      StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs
  7. 6
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallBrowserViewModel.cs
  8. 47
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs
  9. 13
      StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs
  10. 58
      StabilityMatrix.Core/Models/FDS/ComfyUiSelfStartSettings.cs
  11. 380
      StabilityMatrix.Core/Models/FDS/StableSwarmSettings.cs
  12. 5
      StabilityMatrix.Core/Models/PackagePrerequisite.cs
  13. 387
      StabilityMatrix.Core/Models/Packages/StableSwarm.cs
  14. 1
      StabilityMatrix.Core/StabilityMatrix.Core.csproj

3
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -192,7 +192,8 @@ public static class DesignData
null,
null,
null,
null
null,
packageFactory
);
ObservableCacheEx.AddOrUpdate(

109
StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
@ -43,6 +44,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 +91,28 @@ public class UnixPrerequisiteHelper(
{
await InstallNodeIfNecessary(progress);
}
if (prerequisites.Contains(PackagePrerequisite.Dotnet))
{
await InstallDotnetIfNecessary(progress);
}
}
public async Task InstallDotnetIfNecessary(IProgress<ProgressReport>? 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<ProgressReport>? progress = null)
@ -308,6 +343,46 @@ public class UnixPrerequisiteHelper(
onProcessOutput?.Invoke(ProcessOutput.FromStdErrLine(result.StandardError));
}
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("macOS")]
public async Task<Process> RunDotnet(
ProcessArgs args,
string? workingDirectory = null,
Action<ProcessOutput>? onProcessOutput = null,
IReadOnlyDictionary<string, string>? 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")]
[SupportedOSPlatform("macOS")]
public async Task InstallNodeIfNecessary(IProgress<ProgressReport>? progress = null)
@ -353,6 +428,40 @@ public class UnixPrerequisiteHelper(
File.Delete(nodeDownloadPath);
}
private async Task DownloadAndExtractPrerequisite(
IProgress<ProgressReport>? 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<ProgressReport>? progress = null)

112
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<ProgressReport>? progress = null)
{
if (File.Exists(NodeExistsPath))
return;
await DownloadAndExtractPrerequisite(progress, NodeDownloadUrl, NodeDownloadPath, AssetsDir);
var extractedNodeDir = Path.Combine(AssetsDir, "node-v20.11.0-win-x64");
if (Directory.Exists(extractedNodeDir))
{
Logger.Info("node already installed");
Directory.Move(extractedNodeDir, Path.Combine(AssetsDir, "nodejs"));
}
}
[SupportedOSPlatform("windows")]
public async Task InstallDotnetIfNecessary(IProgress<ProgressReport>? progress = null)
{
if (File.Exists(DotnetExistsPath))
return;
await DownloadAndExtractPrerequisite(
progress,
Dotnet7DownloadUrl,
Dotnet7DownloadPath,
DotnetExtractPath
);
await DownloadAndExtractPrerequisite(
progress,
Dotnet8DownloadUrl,
Dotnet8DownloadPath,
DotnetExtractPath
);
}
Logger.Info("Downloading node");
await downloadService.DownloadToFileAsync(NodeDownloadUrl, NodeDownloadPath, progress: progress);
public async Task<Process> RunDotnet(
ProcessArgs args,
string? workingDirectory = null,
Action<ProcessOutput>? onProcessOutput = null,
IReadOnlyDictionary<string, string>? envVars = null,
bool waitForExit = true
)
{
var process = ProcessRunner.StartAnsiProcess(
DotnetExistsPath,
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}"
);
}
private async Task DownloadAndExtractPrerequisite(
IProgress<ProgressReport>? progress,
string downloadUrl,
string downloadPath,
string extractPath
)
{
Logger.Info($"Downloading {downloadUrl} to {downloadPath}");
await downloadService.DownloadToFileAsync(downloadUrl, downloadPath, progress: progress);
Logger.Info("Installing node");
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<ProgressReport>? progress = null)

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

@ -887,6 +887,24 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to ComfyUI is required to install this package. Would you like to install it now?.
/// </summary>
public static string Label_ComfyRequiredDetail {
get {
return ResourceManager.GetString("Label_ComfyRequiredDetail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ComfyUI Required.
/// </summary>
public static string Label_ComfyRequiredTitle {
get {
return ResourceManager.GetString("Label_ComfyRequiredTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Comments.
/// </summary>

6
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -960,4 +960,10 @@
<data name="Label_RecommendedModelsSubText" xml:space="preserve">
<value>While your package is installing, here are some models we recommend to help you get started.</value>
</data>
<data name="Label_ComfyRequiredTitle" xml:space="preserve">
<value>ComfyUI Required</value>
</data>
<data name="Label_ComfyRequiredDetail" xml:space="preserve">
<value>ComfyUI is required to install this package. Would you like to install it now?</value>
</data>
</root>

6
StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs

@ -272,13 +272,19 @@ public partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyn
settingsManager.SaveLaunchArgs(activeInstall.Id, args);
}
if (basePackage is not StableSwarm)
{
await pyRunner.Initialize();
}
// Get path from package
var packagePath = new DirectoryPath(settingsManager.LibraryDir, activeInstall.LibraryPath!);
if (basePackage is not StableSwarm)
{
// Unpack sitecustomize.py to venv
await UnpackSiteCustomize(packagePath.JoinDir("venv"));
}
basePackage.Exited += OnProcessExited;
basePackage.StartupComplete += RunningPackageOnStartupComplete;

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

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
@ -25,6 +26,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
[Transient, ManagedService]
public partial class PackageInstallBrowserViewModel : PageViewModelBase
{
private readonly IPackageFactory packageFactory;
private readonly INavigationService<NewPackageManagerViewModel> packageNavigationService;
private readonly ISettingsManager settingsManager;
private readonly INotificationService notificationService;
@ -56,6 +58,7 @@ public partial class PackageInstallBrowserViewModel : PageViewModelBase
IPrerequisiteHelper prerequisiteHelper
)
{
this.packageFactory = packageFactory;
this.packageNavigationService = packageNavigationService;
this.settingsManager = settingsManager;
this.notificationService = notificationService;
@ -127,7 +130,8 @@ public partial class PackageInstallBrowserViewModel : PageViewModelBase
logger,
pyRunner,
prerequisiteHelper,
packageNavigationService
packageNavigationService,
packageFactory
);
Dispatcher.UIThread.Post(

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

@ -11,6 +11,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
@ -18,6 +19,7 @@ using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
@ -38,7 +40,8 @@ public partial class PackageInstallDetailViewModel(
ILogger<PackageInstallDetailViewModel> logger,
IPyRunner pyRunner,
IPrerequisiteHelper prerequisiteHelper,
INavigationService<NewPackageManagerViewModel> packageNavigationService
INavigationService<NewPackageManagerViewModel> packageNavigationService,
IPackageFactory packageFactory
) : PageViewModelBase
{
public BasePackage SelectedPackage { get; } = package;
@ -126,6 +129,48 @@ public partial class PackageInstallDetailViewModel(
return;
}
if (SelectedPackage is StableSwarm)
{
var comfy = settingsManager.Settings.InstalledPackages.FirstOrDefault(
x => x.PackageName == nameof(ComfyUI)
);
if (comfy == null)
{
// show dialog to install comfy
var dialog = new BetterContentDialog
{
Title = Resources.Label_ComfyRequiredTitle,
Content = Resources.Label_ComfyRequiredDetail,
PrimaryButtonText = Resources.Action_Yes,
CloseButtonText = Resources.Label_No,
DefaultButton = ContentDialogButton.Primary
};
var result = await dialog.ShowAsync();
if (result != ContentDialogResult.Primary)
return;
packageNavigationService.GoBack();
var comfyPackage = packageFactory.FindPackageByName(nameof(ComfyUI));
if (comfyPackage is null)
return;
var vm = new PackageInstallDetailViewModel(
comfyPackage,
settingsManager,
notificationService,
logger,
pyRunner,
prerequisiteHelper,
packageNavigationService,
packageFactory
);
packageNavigationService.NavigateTo(vm);
return;
}
}
InstallName = InstallName.Trim();
var setPackageInstallingStep = new SetPackageInstallingStep(settingsManager, InstallName);

13
StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs

@ -1,4 +1,5 @@
using System.Runtime.Versioning;
using System.Diagnostics;
using System.Runtime.Versioning;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
@ -167,4 +168,14 @@ public interface IPrerequisiteHelper
List<PackagePrerequisite> prerequisites,
IProgress<ProgressReport>? progress = null
);
Task InstallDotnetIfNecessary(IProgress<ProgressReport>? progress = null);
Task<Process> RunDotnet(
ProcessArgs args,
string? workingDirectory = null,
Action<ProcessOutput>? onProcessOutput = null,
IReadOnlyDictionary<string, string>? envVars = null,
bool waitForExit = true
);
}

58
StabilityMatrix.Core/Models/FDS/ComfyUiSelfStartSettings.cs

@ -0,0 +1,58 @@
// The MIT License (MIT)
//
// Copyright (c) 2024 Stability AI
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// from https://github.com/Stability-AI/StableSwarmUI/blob/master/src/BuiltinExtensions/ComfyUIBackend/ComfyUISelfStartBackend.cs
using FreneticUtilities.FreneticDataSyntax;
namespace StabilityMatrix.Core.Models.FDS;
public class ComfyUiSelfStartSettings : AutoConfiguration
{
[ConfigComment(
"The location of the 'main.py' file. Can be an absolute or relative path, but must end with 'main.py'.\nIf you used the installer, this should be 'dlbackend/ComfyUI/main.py'."
)]
public string StartScript = "";
[ConfigComment("Any arguments to include in the launch script.")]
public string ExtraArgs = "";
[ConfigComment(
"If unchecked, the system will automatically add some relevant arguments to the comfy launch. If checked, automatic args (other than port) won't be added."
)]
public bool DisableInternalArgs = false;
[ConfigComment("If checked, will automatically keep the comfy backend up to date when launching.")]
public bool AutoUpdate = true;
[ConfigComment(
"If checked, tells Comfy to generate image previews. If unchecked, previews will not be generated, and images won't show up until they're done."
)]
public bool EnablePreviews = true;
[ConfigComment("Which GPU to use, if multiple are available.")]
public int GPU_ID = 0;
[ConfigComment("How many extra requests may queue up on this backend while one is processing.")]
public int OverQueue = 1;
}

380
StabilityMatrix.Core/Models/FDS/StableSwarmSettings.cs

@ -0,0 +1,380 @@
// The MIT License (MIT)
//
// Copyright (c) 2024 Stability AI
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// from https://raw.githubusercontent.com/Stability-AI/StableSwarmUI/master/src/Core/Settings.cs
using FreneticUtilities.FreneticDataSyntax;
namespace StabilityMatrix.Core.Models.FDS;
/// <summary>Central default settings list.</summary>
public class StableSwarmSettings : AutoConfiguration
{
[ConfigComment("Settings related to file paths.")]
public PathsData Paths = new();
[ConfigComment("Settings related to networking and the webserver.")]
public NetworkData Network = new();
[ConfigComment("Restrictions to apply to default users.")]
public UserRestriction DefaultUserRestriction = new();
[ConfigComment(
"Default settings for users (unless the user modifies them, if so permitted).\n(NOTE: Usually, don't edit this. Go to the 'User' tab to edit your User-Settings)."
)]
public User DefaultUser = new();
[ConfigComment("Settings related to backends.")]
public BackendData Backends = new();
[ConfigComment(
"If this is set to 'true', hides the installer page. If 'false', the installer page will be shown."
)]
public bool IsInstalled = false;
[ConfigComment(
"Ratelimit, in milliseconds, between Nvidia GPU status queries. Default is 1000 ms (1 second)."
)]
public long NvidiaQueryRateLimitMS = 1000;
[ConfigComment(
"How to launch the UI. If 'none', just quietly launch.\nIf 'web', launch your web-browser to the page.\nIf 'webinstall', launch web-browser to the install page.\nIf 'electron', launch the UI in an electron window (NOT YET IMPLEMENTED)."
)]
[ManualSettingsOptions(Impl = null, Vals = new string[] { "none", "web", "webinstall", "electron" })]
public string LaunchMode = "webinstall";
[ConfigComment("The minimum tier of logs that should be visible in the console.\nDefault is 'info'.")]
public string LogLevel = "Info";
[ConfigComment("Settings related to the User Interface.")]
public UIData UI = new();
[ConfigComment("Settings related to webhooks.")]
public WebHooksData WebHooks = new();
/// <summary>Settings related to backends.</summary>
public class BackendData : AutoConfiguration
{
[ConfigComment("How many times to retry initializing a backend before giving up. Default is 3.")]
public int MaxBackendInitAttempts = 3;
[ConfigComment(
"Safety check, the maximum duration all requests can be waiting for a backend before the system declares a backend handling failure."
)]
public int MaxTimeoutMinutes = 20;
[ConfigComment(
"The maximum duration an individual request can be waiting on a backend to be available before giving up.\n"
+ "Not to be confused with 'MaxTimeoutMinutes' which requires backends be unresponsive for that duration, this duration includes requests that are merely waiting because other requests are queued."
+ "\nDefaults to 60 * 24 * 7 = 1 week (ultra-long max queue duration)."
)]
public int PerRequestTimeoutMinutes = 60 * 24 * 7;
[ConfigComment(
"The maximum number of pending requests to continue forcing orderly processing of.\nOver this limit, requests may start going out of order."
)]
public int MaxRequestsForcedOrder = 20;
[ConfigComment(
"How many minutes to wait after the last generation before automatically freeing up VRAM (to prevent issues with other programs).\nThis has the downside of a small added bit of time to load back onto VRAM at next usage.\nUse a decimal number to free after seconds.\nDefaults to 10 minutes."
)]
public double ClearVRAMAfterMinutes = 10;
[ConfigComment(
"How many minutes to wait after the last generation before automatically freeing up system RAM (to prevent issues with other programs).\nThis has the downside of causing models to fully load from data drive at next usage.\nUse a decimal number to free after seconds.\nDefaults to 60 minutes (one hour)."
)]
public double ClearSystemRAMAfterMinutes = 60;
}
/// <summary>Settings related to networking and the webserver.</summary>
public class NetworkData : AutoConfiguration
{
[ConfigComment(
"What web host address to use. `localhost` means your PC only."
+ "\nLinux users may use `0.0.0.0` to mean accessible to anyone that can connect to your PC (ie LAN users, or the public if your firewall is open)."
+ "\nWindows users may use `*` for that, though it may require additional Windows firewall configuration."
+ "\nAdvanced server users may wish to manually specify a host bind address here."
)]
public string Host = "localhost";
[ConfigComment("What web port to use. Default is '7801'.")]
public int Port = 7801;
[ConfigComment(
"If true, if the port is already in use, the server will try to find another port to use instead.\nIf false, the server will fail to start if the port is already in use."
)]
public bool PortCanChange = true;
[ConfigComment(
"Backends are automatically assigned unique ports. This value selects which port number to start the assignment from.\nDefault is '7820'."
)]
public int BackendStartingPort = 7820;
}
/// <summary>Settings related to file paths.</summary>
public class PathsData : AutoConfiguration
{
[ConfigComment(
"Root path for model files. Use a full-formed path (starting with '/' or a Windows drive like 'C:') to use an absolute path.\nDefaults to 'Models'."
)]
public string ModelRoot = "Models";
[ConfigComment(
"The model folder to use within 'ModelRoot'.\nDefaults to 'Stable-Diffusion'.\nAbsolute paths work too."
)]
public string SDModelFolder = "Stable-Diffusion";
[ConfigComment(
"The LoRA (or related adapter type) model folder to use within 'ModelRoot'.\nDefaults to 'Lora'.\nAbsolute paths work too."
)]
public string SDLoraFolder = "Lora";
[ConfigComment(
"The VAE (autoencoder) model folder to use within 'ModelRoot'.\nDefaults to 'VAE'.\nAbsolute paths work too."
)]
public string SDVAEFolder = "VAE";
[ConfigComment(
"The Embedding (eg textual inversion) model folder to use within 'ModelRoot'.\nDefaults to 'Embeddings'.\nAbsolute paths work too."
)]
public string SDEmbeddingFolder = "Embeddings";
[ConfigComment(
"The ControlNets model folder to use within 'ModelRoot'.\nDefaults to 'controlnet'.\nAbsolute paths work too."
)]
public string SDControlNetsFolder = "controlnet";
[ConfigComment(
"The CLIP Vision model folder to use within 'ModelRoot'.\nDefaults to 'clip_vision'.\nAbsolute paths work too."
)]
public string SDClipVisionFolder = "clip_vision";
[ConfigComment("Root path for data (user configs, etc).\nDefaults to 'Data'")]
public string DataPath = "Data";
[ConfigComment("Root path for output files (images, etc).\nDefaults to 'Output'")]
public string OutputPath = "Output";
[ConfigComment("The folder for wildcard (.txt) files, under Data.\nDefaults to 'Wildcards'")]
public string WildcardsFolder = "Wildcards";
[ConfigComment(
"When true, output paths always have the username as a folder.\nWhen false, this will be skipped.\nKeep this on in multi-user environments."
)]
public bool AppendUserNameToOutputPath = true;
}
/// <summary>Settings to control restrictions on users.</summary>
public class UserRestriction : AutoConfiguration
{
[ConfigComment("How many directories deep a user's custom OutPath can be.\nDefault is 5.")]
public int MaxOutPathDepth = 5;
[ConfigComment("Which user-settings the user is allowed to modify.\nDefault is all of them.")]
public List<string> AllowedSettings = new() { "*" };
[ConfigComment(
"If true, the user is treated as a full admin.\nThis includes the ability to modify these settings."
)]
public bool Admin = false;
[ConfigComment("If true, user may load models.\nIf false, they may only use already-loaded models.")]
public bool CanChangeModels = true;
[ConfigComment(
"What models are allowed, as a path regex.\nDirectory-separator is always '/'. Can be '.*' for all, 'MyFolder/.*' for only within that folder, etc.\nDefault is all."
)]
public string AllowedModels = ".*";
[ConfigComment("Generic permission flags. '*' means all.\nDefault is all.")]
public List<string> PermissionFlags = new() { "*" };
[ConfigComment("How many images can try to be generating at the same time on this user.")]
public int MaxT2ISimultaneous = 32;
}
/// <summary>Settings per-user.</summary>
public class User : AutoConfiguration
{
public class OutPath : AutoConfiguration
{
[ConfigComment(
"Builder for output file paths. Can use auto-filling placeholders like '[model]' for the model name, '[prompt]' for a snippet of prompt text, etc.\n"
+ "Full details in the docs: https://github.com/Stability-AI/StableSwarmUI/blob/master/docs/User%20Settings.md#path-format"
)]
public string Format = "raw/[year]-[month]-[day]/[hour][minute]-[prompt]-[model]-[seed]";
[ConfigComment("How long any one part can be.\nDefault is 40 characters.")]
public int MaxLenPerPart = 40;
}
[ConfigComment("Settings related to output path building.")]
public OutPath OutPathBuilder = new();
public class FileFormatData : AutoConfiguration
{
[ConfigComment("What format to save images in.\nDefault is '.jpg' (at 100% quality).")]
public string ImageFormat = "JPG";
[ConfigComment("Whether to store metadata into saved images.\nDefaults enabled.")]
public bool SaveMetadata = true;
[ConfigComment(
"If set to non-0, adds DPI metadata to saved images.\n'72' is a good value for compatibility with some external software."
)]
public int DPI = 0;
[ConfigComment(
"If set to true, a '.txt' file will be saved alongside images with the image metadata easily viewable.\nThis can work even if saving in the image is disabled. Defaults disabled."
)]
public bool SaveTextFileMetadata = false;
}
[ConfigComment("Settings related to saved file format.")]
public FileFormatData FileFormat = new();
[ConfigComment("Whether your files save to server data drive or not.")]
public bool SaveFiles = true;
[ConfigComment("If true, folders will be discard from starred image paths.")]
public bool StarNoFolders = false;
[ConfigComment("What theme to use. Default is 'dark_dreams'.")]
public string Theme = "dark_dreams";
[ConfigComment(
"If enabled, batch size will be reset to 1 when parameters are loaded.\nThis can prevent accidents that might thrash your GPU or cause compatibility issues, especially for example when importing a comfy workflow.\nYou can still set the batch size at will in the GUI."
)]
public bool ResetBatchSizeToOne = false;
public enum HintFormatOptions
{
BUTTON,
HOVER,
NONE
}
[ConfigComment("The format for parameter hints to display as.\nDefault is 'BUTTON'.")]
[SettingsOptions(Impl = typeof(SettingsOptionsAttribute.ForEnum<HintFormatOptions>))]
public string HintFormat = "BUTTON";
public class VAEsData : AutoConfiguration
{
[ConfigComment(
"What VAE to use with SDXL models by default. Use 'None' to use the one in the model."
)]
[ManualSettingsOptions(Impl = null, Vals = new string[] { "None" })]
public string DefaultSDXLVAE = "None";
[ConfigComment(
"What VAE to use with SDv1 models by default. Use 'None' to use the one in the model."
)]
[ManualSettingsOptions(Impl = null, Vals = new string[] { "None" })]
public string DefaultSDv1VAE = "None";
}
[ConfigComment("Options to override default VAEs with.")]
public VAEsData VAEs = new();
[ConfigComment(
"When generating live previews, this is how many simultaneous generation requests can be waiting at one time."
)]
public int MaxSimulPreviews = 1;
[ConfigComment(
"Set to a number above 1 to allow generations of multiple images to automatically generate square mini-grids when they're done."
)]
public int MaxImagesInMiniGrid = 1;
[ConfigComment("How many images the history view should stop trying to load after.")]
public int MaxImagesInHistory = 1000;
[ConfigComment(
"If true, the Image History view will cache small preview thumbnails of images.\nThis should make things run faster. You can turn it off if you don't want that."
)]
public bool ImageHistoryUsePreviews = true;
[ConfigComment(
"Delay, in seconds, betweeen Generate Forever updates.\nIf the delay hits and a generation is still waiting, it will be skipped.\nDefault is 0.1 seconds."
)]
public double GenerateForeverDelay = 0.1;
[ConfigComment("What language to display the UI in.\nDefault is 'en' (English).")]
public string Language = "en";
}
/// <summary>UI-related settings.</summary>
public class UIData : AutoConfiguration
{
[ConfigComment(
"Optionally specify a (raw HTML) welcome message here. If specified, will override the automatic welcome messages."
)]
public string OverrideWelcomeMessage = "";
}
/// <summary>Webhook settings.</summary>
public class WebHooksData : AutoConfiguration
{
[ConfigComment(
"Webhook to call (empty JSON POST) when queues are starting up from idle.\nLeave empty to disable any webhook.\nCall must return before the first generation starts."
)]
public string QueueStartWebhook = "";
[ConfigComment(
"Webhook to call (empty JSON POST) when all queues are done and the server is going idle.\nLeave empty to disable any webhook.\nCall must return before queuing may restart."
)]
public string QueueEndWebhook = "";
[ConfigComment(
"How long to wait (in seconds) after all queues are done before sending the queue end webhook.\nThis is useful to prevent rapid start+end calls."
)]
public double QueueEndDelay = 1;
}
}
[AttributeUsage(AttributeTargets.Field)]
public class SettingsOptionsAttribute : Attribute
{
public abstract class AbstractImpl
{
public abstract string[] GetOptions { get; }
}
public class ForEnum<T> : AbstractImpl
where T : Enum
{
public override string[] GetOptions => Enum.GetNames(typeof(T));
}
public Type Impl;
public virtual string[] Options => (Activator.CreateInstance(Impl) as AbstractImpl).GetOptions;
}
[AttributeUsage(AttributeTargets.Field)]
public class ManualSettingsOptionsAttribute : SettingsOptionsAttribute
{
public string[] Vals;
public override string[] Options => Vals;
}

5
StabilityMatrix.Core/Models/PackagePrerequisite.cs

@ -6,7 +6,6 @@ public enum PackagePrerequisite
VcRedist,
Git,
Node,
Dotnet7,
Dotnet8,
Tkinter,
Dotnet,
Tkinter
}

387
StabilityMatrix.Core/Models/Packages/StableSwarm.cs

@ -0,0 +1,387 @@
using System.Configuration;
using System.Diagnostics;
using System.Text.RegularExpressions;
using FreneticUtilities.FreneticDataSyntax;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Models.FDS;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages;
[Singleton(typeof(BasePackage))]
public class StableSwarm(
IGithubApiCache githubApi,
ISettingsManager settingsManager,
IDownloadService downloadService,
IPrerequisiteHelper prerequisiteHelper
) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper)
{
private Process? dotnetProcess;
public override string Name => "StableSwarmUI";
public override string DisplayName { get; set; } = "StableSwarmUI";
public override string Author => "Stability-AI";
public override string Blurb =>
"A Modular Stable Diffusion Web-User-Interface, with an emphasis on making powertools easily accessible, high performance, and extensibility.";
public override string LicenseType => "MIT";
public override string LicenseUrl =>
"https://github.com/Stability-AI/StableSwarmUI/blob/master/LICENSE.txt";
public override string LaunchCommand => string.Empty;
public override Uri PreviewImageUri =>
new(
"https://raw.githubusercontent.com/Stability-AI/StableSwarmUI/master/.github/images/stableswarmui.jpg"
);
public override string OutputFolderName => "Output";
public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>
[SharedFolderMethod.Symlink, SharedFolderMethod.Configuration, SharedFolderMethod.None];
public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;
public override bool OfferInOneClickInstaller => false;
public override List<LaunchOptionDefinition> LaunchOptions =>
[
new LaunchOptionDefinition
{
Name = "Host",
Type = LaunchOptionType.String,
DefaultValue = "127.0.0.1",
Options = ["--host"]
},
new LaunchOptionDefinition
{
Name = "Port",
Type = LaunchOptionType.String,
DefaultValue = "7801",
Options = ["--port"]
},
new LaunchOptionDefinition
{
Name = "Ngrok Path",
Type = LaunchOptionType.String,
Options = ["--ngrok-path"]
},
new LaunchOptionDefinition
{
Name = "Ngrok Basic Auth",
Type = LaunchOptionType.String,
Options = ["--ngrok-basic-auth"]
},
new LaunchOptionDefinition
{
Name = "Cloudflared Path",
Type = LaunchOptionType.String,
Options = ["--cloudflared-path"]
},
new LaunchOptionDefinition
{
Name = "Proxy Region",
Type = LaunchOptionType.String,
Options = ["--proxy-region"]
},
new LaunchOptionDefinition
{
Name = "Launch Mode",
Type = LaunchOptionType.Bool,
Options = ["--launch-mode web", "--launch-mode webinstall"]
},
LaunchOptionDefinition.Extras
];
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>
new()
{
[SharedFolderType.StableDiffusion] = ["Models/Stable-Diffusion"],
[SharedFolderType.Lora] = ["Models/Lora"],
[SharedFolderType.VAE] = ["Models/VAE"],
[SharedFolderType.TextualInversion] = ["Models/Embeddings"],
[SharedFolderType.ControlNet] = ["Models/controlnet"],
[SharedFolderType.InvokeClipVision] = ["Models/clip_vision"]
};
public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>
new() { [SharedOutputType.Text2Img] = [OutputFolderName] };
public override string MainBranch => "master";
public override bool ShouldIgnoreReleases => true;
public override IEnumerable<TorchVersion> AvailableTorchVersions =>
[TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Rocm, TorchVersion.Mps];
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;
public override IEnumerable<PackagePrerequisite> Prerequisites =>
[
PackagePrerequisite.Git,
PackagePrerequisite.Dotnet,
PackagePrerequisite.Python310,
PackagePrerequisite.VcRedist
];
private FilePath GetSettingsPath(string installLocation) =>
Path.Combine(installLocation, "Data", "Settings.fds");
private FilePath GetBackendsPath(string installLocation) =>
Path.Combine(installLocation, "Data", "Backends.fds");
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
SharedFolderMethod selectedSharedFolderMethod,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing StableSwarmUI...", isIndeterminate: true));
var comfy = settingsManager.Settings.InstalledPackages.FirstOrDefault(
x => x.PackageName == nameof(ComfyUI)
);
if (comfy == null)
{
throw new InvalidOperationException("ComfyUI must be installed to use StableSwarmUI");
}
try
{
await prerequisiteHelper
.RunDotnet(
[
"nuget",
"add",
"source",
"https://api.nuget.org/v3/index.json",
"--name",
"\"NuGet official package source\""
],
workingDirectory: installLocation,
onProcessOutput: onConsoleOutput
)
.ConfigureAwait(false);
}
catch (ProcessException e)
{
// ignore, probably means the source is already there
}
await prerequisiteHelper
.RunDotnet(
[
"build",
"src/StableSwarmUI.csproj",
"--configuration",
"Release",
"-o",
"src/bin/live_release"
],
workingDirectory: installLocation,
onProcessOutput: onConsoleOutput
)
.ConfigureAwait(false);
// set default settings
var settings = new StableSwarmSettings { IsInstalled = true };
if (selectedSharedFolderMethod is SharedFolderMethod.Configuration)
{
settings.Paths = new StableSwarmSettings.PathsData
{
ModelRoot = settingsManager.ModelsDirectory,
SDModelFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.StableDiffusion.ToString()
),
SDLoraFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.Lora.ToString()
),
SDVAEFolder = Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.VAE.ToString()),
SDEmbeddingFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.TextualInversion.ToString()
),
SDControlNetsFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.ControlNet.ToString()
),
SDClipVisionFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.InvokeClipVision.ToString()
)
};
}
settings.Save(true).SaveToFile(GetSettingsPath(installLocation));
var backendsFile = new FDSSection();
var dataSection = new FDSSection();
dataSection.Set("type", "comfyui_selfstart");
dataSection.Set("title", "StabilityMatrix ComfyUI Self-Start");
dataSection.Set("enabled", true);
var launchArgs = comfy.LaunchArgs ?? [];
var comfyArgs = string.Join(
' ',
launchArgs
.Select(arg => arg.ToArgString()?.TrimEnd())
.Where(arg => !string.IsNullOrWhiteSpace(arg))
);
dataSection.Set(
"settings",
new ComfyUiSelfStartSettings
{
StartScript = $"../{comfy.DisplayName}/main.py",
DisableInternalArgs = false,
AutoUpdate = false,
ExtraArgs = comfyArgs
}.Save(true)
);
backendsFile.Set("0", dataSection);
backendsFile.SaveToFile(GetBackendsPath(installLocation));
}
public override async Task RunPackage(
string installedPackagePath,
string command,
string arguments,
Action<ProcessOutput>? onConsoleOutput
)
{
var aspEnvVars = new Dictionary<string, string>
{
["ASPNETCORE_ENVIRONMENT"] = "Production",
["ASPNETCORE_URLS"] = "http://*:7801"
};
void HandleConsoleOutput(ProcessOutput s)
{
onConsoleOutput?.Invoke(s);
if (s.Text.Contains("Starting webserver", StringComparison.OrdinalIgnoreCase))
{
var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)");
var match = regex.Match(s.Text);
if (match.Success)
{
WebUrl = match.Value;
}
OnStartupComplete(WebUrl);
}
}
dotnetProcess = await prerequisiteHelper
.RunDotnet(
args: $"src\\bin\\live_release\\StableSwarmUI.dll {arguments.TrimEnd()}",
workingDirectory: installedPackagePath,
envVars: aspEnvVars,
onProcessOutput: HandleConsoleOutput,
waitForExit: false
)
.ConfigureAwait(false);
}
public override Task SetupModelFolders(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
) =>
sharedFolderMethod switch
{
SharedFolderMethod.Symlink
=> base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink),
SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory), // TODO
_ => Task.CompletedTask
};
public override Task RemoveModelFolderLinks(
DirectoryPath installDirectory,
SharedFolderMethod sharedFolderMethod
) =>
sharedFolderMethod switch
{
SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod),
SharedFolderMethod.Configuration => RemoveModelFoldersConfig(installDirectory),
_ => Task.CompletedTask
};
public override async Task WaitForShutdown()
{
if (dotnetProcess is { HasExited: false })
{
dotnetProcess.Kill(true);
try
{
await dotnetProcess
.WaitForExitAsync(new CancellationTokenSource(5000).Token)
.ConfigureAwait(false);
}
catch (OperationCanceledException e)
{
Console.WriteLine(e);
}
}
dotnetProcess = null;
GC.SuppressFinalize(this);
}
private Task SetupModelFoldersConfig(DirectoryPath installDirectory)
{
var settingsPath = GetSettingsPath(installDirectory);
var existingSettings = new StableSwarmSettings();
var settingsExists = File.Exists(settingsPath);
if (settingsExists)
{
var section = FDSUtility.ReadFile(settingsPath);
existingSettings.Load(section);
}
existingSettings.Paths = new StableSwarmSettings.PathsData
{
ModelRoot = settingsManager.ModelsDirectory,
SDModelFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.StableDiffusion.ToString()
),
SDLoraFolder = Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.Lora.ToString()),
SDVAEFolder = Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.VAE.ToString()),
SDEmbeddingFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.TextualInversion.ToString()
),
SDControlNetsFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.ControlNet.ToString()
),
SDClipVisionFolder = Path.Combine(
settingsManager.ModelsDirectory,
SharedFolderType.InvokeClipVision.ToString()
)
};
existingSettings.Save(true).SaveToFile(settingsPath);
return Task.CompletedTask;
}
private Task RemoveModelFoldersConfig(DirectoryPath installDirectory)
{
var settingsPath = GetSettingsPath(installDirectory);
var existingSettings = new StableSwarmSettings();
var settingsExists = File.Exists(settingsPath);
if (settingsExists)
{
var section = FDSUtility.ReadFile(settingsPath);
existingSettings.Load(section);
}
existingSettings.Paths = new StableSwarmSettings.PathsData();
existingSettings.Save(true).SaveToFile(settingsPath);
return Task.CompletedTask;
}
}

1
StabilityMatrix.Core/StabilityMatrix.Core.csproj

@ -29,6 +29,7 @@
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
<PackageReference Include="DynamicData" Version="8.3.27" />
<PackageReference Include="ExifLibNet" Version="2.1.4" />
<PackageReference Include="FreneticLLC.FreneticUtilities" Version="1.0.24" />
<PackageReference Include="Hardware.Info" Version="100.0.1.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="KGySoft.Drawing.Core" Version="8.0.0" />

Loading…
Cancel
Save