Multi-Platform Package Manager for Stable Diffusion
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

386 lines
14 KiB

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{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}live_release{Path.DirectorySeparatorChar}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;
}
}