diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a785a7..0bf1e01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,12 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - This will link the package's output folders to the relevant subfolders in the "Outputs" directory - When a package only has a generic "outputs" folder, all generated images from that package will be linked to the "Outputs\Text2Img" folder when this option is enabled - Added "Outputs" page for viewing generated images from any package, or the shared output folder +- Added [Stable Diffusion WebUI/UX](https://github.com/anapnoe/stable-diffusion-webui-ux) package +### Changed +- If ComfyUI for Inference is chosen during the One-Click Installer, the Inference page will be opened after installation instead of the Launch page ### Fixed - Fixed crash when clicking Inference gallery image after the image is deleted externally in file explorer +- Fixed Inference popup Install button not working on One-Click Installer ## v2.5.3 ### Added diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index b28534c5..25f8a52b 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -433,12 +433,13 @@ public sealed class App : Application internal static void ConfigurePackages(IServiceCollection services) { services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); } private static IServiceCollection ConfigureServices() diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs index b4d4a823..9720cc8b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; using StabilityMatrix.Avalonia.Languages; +using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Factory; @@ -26,6 +27,7 @@ public partial class OneClickInstallViewModel : ViewModelBase private readonly ILogger logger; private readonly IPyRunner pyRunner; private readonly ISharedFolders sharedFolders; + private readonly INavigationService navigationService; private const string DefaultPackageName = "stable-diffusion-webui"; [ObservableProperty] @@ -55,13 +57,16 @@ public partial class OneClickInstallViewModel : ViewModelBase public bool IsProgressBarVisible => OneClickInstallProgress > 0 || IsIndeterminate; + private bool isInferenceInstall; + public OneClickInstallViewModel( ISettingsManager settingsManager, IPackageFactory packageFactory, IPrerequisiteHelper prerequisiteHelper, ILogger logger, IPyRunner pyRunner, - ISharedFolders sharedFolders + ISharedFolders sharedFolders, + INavigationService navigationService ) { this.settingsManager = settingsManager; @@ -70,6 +75,7 @@ public partial class OneClickInstallViewModel : ViewModelBase this.logger = logger; this.pyRunner = pyRunner; this.sharedFolders = sharedFolders; + this.navigationService = navigationService; HeaderText = Resources.Text_WelcomeToStabilityMatrix; SubHeaderText = Resources.Text_OneClickInstaller_SubHeader; @@ -95,6 +101,18 @@ public partial class OneClickInstallViewModel : ViewModelBase return Task.CompletedTask; } + [RelayCommand] + private async Task InstallComfyForInference() + { + var comfyPackage = AllPackages.FirstOrDefault(x => x is ComfyUI); + if (comfyPackage != null) + { + SelectedPackage = comfyPackage; + isInferenceInstall = true; + await InstallCommand.ExecuteAsync(null); + } + } + private async Task DoInstall() { HeaderText = $"{Resources.Label_Installing} {SelectedPackage.DisplayName}"; @@ -171,14 +189,18 @@ public partial class OneClickInstallViewModel : ViewModelBase SubSubHeaderText = string.Empty; OneClickInstallProgress = 100; - for (var i = 0; i < 3; i++) + for (var i = 3; i > 0; i--) { - SubHeaderText = $"{Resources.Text_ProceedingToLaunchPage} ({i + 1}s)"; + SubHeaderText = $"{Resources.Text_ProceedingToLaunchPage} ({i}s)"; await Task.Delay(1000); } // should close dialog EventManager.Instance.OnOneClickInstallFinished(false); + if (isInferenceInstall) + { + navigationService.NavigateTo(); + } } private async Task DownloadPackage( diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index 1abe5f17..0bd3f1cf 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs @@ -108,7 +108,7 @@ public partial class MainWindowViewModel : ViewModelBase var startupTime = CodeTimer.FormatTime(Program.StartupTimer.Elapsed); Logger.Info($"App started ({startupTime})"); - if (Program.Args.DebugOneClickInstall || !settingsManager.Settings.InstalledPackages.Any()) + if (Program.Args.DebugOneClickInstall || settingsManager.Settings.InstalledPackages.Any()) { var viewModel = dialogFactory.Get(); var dialog = new BetterContentDialog diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml index d3792f60..02316e69 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml @@ -26,6 +26,7 @@ FontSize="24" Margin="0, 16, 0, 4" /> - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml index 1f1e6e62..b114150c 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml @@ -37,8 +37,10 @@ Title="Use ComfyUI with Inference" Subtitle="A new built-in native Stable Diffusion experience, powered by ComfyUI" ActionButtonContent="{x:Static lang:Resources.Action_Install}" + ActionButtonCommand="{Binding InstallComfyForInferenceCommand}" CloseButtonContent="{x:Static lang:Resources.Action_Close}" PreferredPlacement="RightTop" + Margin="8,0,0,0" PlacementMargin="0,0,0,0" TailVisibility="Auto"> @@ -68,6 +70,7 @@ FontSize="24" Margin="16, 16, 0, 4"/> - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml.cs index 3e6ff511..23c8a034 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml.cs @@ -30,7 +30,12 @@ public partial class OneClickInstallDialog : UserControl var teachingTip = this.FindControl("InferenceTeachingTip") ?? throw new InvalidOperationException("TeachingTip not found"); - ; + + teachingTip.ActionButtonClick += (_, _) => + { + teachingTip.IsOpen = false; + }; + // Find ComfyUI listbox item var listBox = this.FindControl("PackagesListBox"); diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs index 546bd4d7..5345b881 100644 --- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs @@ -187,7 +187,7 @@ public abstract class BasePackage await venvRunner .PipInstall(PyVenvRunner.TorchPipInstallArgsCuda, onConsoleOutput) .ConfigureAwait(false); - await venvRunner.PipInstall("xformers", onConsoleOutput).ConfigureAwait(false); + await venvRunner.PipInstall("xformers==0.0.20", onConsoleOutput).ConfigureAwait(false); } protected Task InstallDirectMlTorch( diff --git a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs new file mode 100644 index 00000000..97d1f9da --- /dev/null +++ b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs @@ -0,0 +1,289 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using NLog; +using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Progress; +using StabilityMatrix.Core.Processes; +using StabilityMatrix.Core.Python; +using StabilityMatrix.Core.Services; + +namespace StabilityMatrix.Core.Models.Packages; + +public class StableDiffusionUx : BaseGitPackage +{ + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public override string Name => "stable-diffusion-webui-ux"; + public override string DisplayName { get; set; } = "Stable Diffusion Web UI-UX"; + public override string Author => "anapnoe"; + public override string LicenseType => "AGPL-3.0"; + public override string LicenseUrl => + "https://github.com/anapnoe/stable-diffusion-webui-ux/blob/master/LICENSE.txt"; + public override string Blurb => + "A pixel perfect design, mobile friendly, customizable interface that adds accessibility, " + + "ease of use and extended functionallity to the stable diffusion web ui."; + public override string LaunchCommand => "launch.py"; + public override Uri PreviewImageUri => + new( + "https://user-images.githubusercontent.com/124302297/227973574-6003142d-0c7c-41c6-9966-0792a94549e9.png" + ); + + public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; + + public StableDiffusionUx( + IGithubApiCache githubApi, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) + : base(githubApi, settingsManager, downloadService, prerequisiteHelper) { } + + public override Dictionary> SharedFolders => + new() + { + [SharedFolderType.StableDiffusion] = new[] { "models/Stable-diffusion" }, + [SharedFolderType.ESRGAN] = new[] { "models/ESRGAN" }, + [SharedFolderType.RealESRGAN] = new[] { "models/RealESRGAN" }, + [SharedFolderType.SwinIR] = new[] { "models/SwinIR" }, + [SharedFolderType.Lora] = new[] { "models/Lora" }, + [SharedFolderType.LyCORIS] = new[] { "models/LyCORIS" }, + [SharedFolderType.ApproxVAE] = new[] { "models/VAE-approx" }, + [SharedFolderType.VAE] = new[] { "models/VAE" }, + [SharedFolderType.DeepDanbooru] = new[] { "models/deepbooru" }, + [SharedFolderType.Karlo] = new[] { "models/karlo" }, + [SharedFolderType.TextualInversion] = new[] { "embeddings" }, + [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, + [SharedFolderType.ControlNet] = new[] { "models/ControlNet" }, + [SharedFolderType.Codeformer] = new[] { "models/Codeformer" }, + [SharedFolderType.LDSR] = new[] { "models/LDSR" }, + [SharedFolderType.AfterDetailer] = new[] { "models/adetailer" } + }; + + public override Dictionary>? SharedOutputFolders => + new() + { + [SharedOutputType.Extras] = new[] { "outputs/extras-images" }, + [SharedOutputType.Saved] = new[] { "log/images" }, + [SharedOutputType.Img2Img] = new[] { "outputs/img2img-images" }, + [SharedOutputType.Text2Img] = new[] { "outputs/text2img-images" }, + [SharedOutputType.Img2ImgGrids] = new[] { "outputs/img2img-grids" }, + [SharedOutputType.Text2ImgGrids] = new[] { "outputs/text2img-grids" } + }; + + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")] + public override List LaunchOptions => + new() + { + new() + { + Name = "Host", + Type = LaunchOptionType.String, + DefaultValue = "localhost", + Options = new() { "--server-name" } + }, + new() + { + Name = "Port", + Type = LaunchOptionType.String, + DefaultValue = "7860", + Options = new() { "--port" } + }, + new() + { + Name = "VRAM", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper + .IterGpuInfo() + .Select(gpu => gpu.MemoryLevel) + .Max() switch + { + Level.Low => "--lowvram", + Level.Medium => "--medvram", + _ => null + }, + Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" } + }, + new() + { + Name = "Xformers", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper.HasNvidiaGpu(), + Options = new() { "--xformers" } + }, + new() + { + Name = "API", + Type = LaunchOptionType.Bool, + InitialValue = true, + Options = new() { "--api" } + }, + new() + { + Name = "Auto Launch Web UI", + Type = LaunchOptionType.Bool, + InitialValue = false, + Options = new() { "--autolaunch" } + }, + new() + { + Name = "Skip Torch CUDA Check", + Type = LaunchOptionType.Bool, + InitialValue = !HardwareHelper.HasNvidiaGpu(), + Options = new() { "--skip-torch-cuda-test" } + }, + new() + { + Name = "Skip Python Version Check", + Type = LaunchOptionType.Bool, + InitialValue = true, + Options = new() { "--skip-python-version-check" } + }, + new() + { + Name = "No Half", + Type = LaunchOptionType.Bool, + Description = "Do not switch the model to 16-bit floats", + InitialValue = HardwareHelper.HasAmdGpu(), + Options = new() { "--no-half" } + }, + new() + { + Name = "Skip SD Model Download", + Type = LaunchOptionType.Bool, + InitialValue = false, + Options = new() { "--no-download-sd-model" } + }, + new() + { + Name = "Skip Install", + Type = LaunchOptionType.Bool, + Options = new() { "--skip-install" } + }, + LaunchOptionDefinition.Extras + }; + + public override IEnumerable AvailableSharedFolderMethods => + new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None }; + + public override IEnumerable AvailableTorchVersions => + new[] { TorchVersion.Cpu, TorchVersion.Cuda, TorchVersion.DirectMl, TorchVersion.Rocm }; + + public override Task GetLatestVersion() => Task.FromResult("master"); + + public override bool ShouldIgnoreReleases => true; + + public override string OutputFolderName => "outputs"; + + public override async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + IProgress? progress = null, + Action? onConsoleOutput = null + ) + { + await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false); + + progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true)); + // Setup venv + await using var venvRunner = new PyVenvRunner(Path.Combine(installLocation, "venv")); + venvRunner.WorkingDirectory = installLocation; + await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false); + + switch (torchVersion) + { + case TorchVersion.Cpu: + await InstallCpuTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); + break; + case TorchVersion.Cuda: + await InstallCudaTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); + break; + case TorchVersion.Rocm: + await InstallRocmTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); + break; + case TorchVersion.DirectMl: + await InstallDirectMlTorch(venvRunner, progress, onConsoleOutput) + .ConfigureAwait(false); + break; + default: + throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null); + } + + // Install requirements file + progress?.Report( + new ProgressReport(-1f, "Installing Package Requirements", isIndeterminate: true) + ); + Logger.Info("Installing requirements_versions.txt"); + + var requirements = new FilePath(installLocation, "requirements_versions.txt"); + await venvRunner + .PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch") + .ConfigureAwait(false); + + progress?.Report( + new ProgressReport(1f, "Installing Package Requirements", isIndeterminate: false) + ); + + progress?.Report(new ProgressReport(-1f, "Updating configuration", isIndeterminate: true)); + + // // Create and add {"show_progress_type": "TAESD"} to config.json + // // Only add if the file doesn't exist + // var configPath = Path.Combine(installLocation, "config.json"); + // if (!File.Exists(configPath)) + // { + // var config = new JsonObject { { "show_progress_type", "TAESD" } }; + // await File.WriteAllTextAsync(configPath, config.ToString()).ConfigureAwait(false); + // } + + progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false)); + } + + public override async Task RunPackage( + string installedPackagePath, + string command, + string arguments, + Action? onConsoleOutput + ) + { + await SetupVenv(installedPackagePath).ConfigureAwait(false); + + void HandleConsoleOutput(ProcessOutput s) + { + onConsoleOutput?.Invoke(s); + + if (!s.Text.Contains("Running on", StringComparison.OrdinalIgnoreCase)) + return; + + var regex = new Regex(@"(https?:\/\/)([^:\s]+):(\d+)"); + var match = regex.Match(s.Text); + if (!match.Success) + return; + + WebUrl = match.Value; + OnStartupComplete(WebUrl); + } + + var args = $"\"{Path.Combine(installedPackagePath, command)}\" {arguments}"; + + VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit); + } + + private async Task InstallRocmTorch( + PyVenvRunner venvRunner, + IProgress? progress = null, + Action? onConsoleOutput = null + ) + { + progress?.Report( + new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true) + ); + + await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); + + await venvRunner + .PipInstall(PyVenvRunner.TorchPipInstallArgsRocm511, onConsoleOutput) + .ConfigureAwait(false); + } +}