using System; using System.Collections.ObjectModel; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Factory; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; public partial class OneClickInstallViewModel : ViewModelBase { private readonly ISettingsManager settingsManager; private readonly IPackageFactory packageFactory; private readonly IPrerequisiteHelper prerequisiteHelper; private readonly ILogger logger; private readonly IPyRunner pyRunner; private readonly ISharedFolders sharedFolders; private const string DefaultPackageName = "stable-diffusion-webui"; [ObservableProperty] private string headerText; [ObservableProperty] private string subHeaderText; [ObservableProperty] private string subSubHeaderText = string.Empty; [ObservableProperty] private bool showInstallButton; [ObservableProperty] private bool isIndeterminate; [ObservableProperty] private ObservableCollection allPackages; [ObservableProperty] private BasePackage selectedPackage; [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsProgressBarVisible))] private int oneClickInstallProgress; public bool IsProgressBarVisible => OneClickInstallProgress > 0 || IsIndeterminate; public OneClickInstallViewModel(ISettingsManager settingsManager, IPackageFactory packageFactory, IPrerequisiteHelper prerequisiteHelper, ILogger logger, IPyRunner pyRunner, ISharedFolders sharedFolders) { this.settingsManager = settingsManager; this.packageFactory = packageFactory; this.prerequisiteHelper = prerequisiteHelper; this.logger = logger; this.pyRunner = pyRunner; this.sharedFolders = sharedFolders; HeaderText = "Welcome to Stability Matrix!"; SubHeaderText = "Choose your preferred interface and click Install to get started!"; ShowInstallButton = true; AllPackages = new ObservableCollection(this.packageFactory.GetAllAvailablePackages()); SelectedPackage = AllPackages[0]; } [RelayCommand] private async Task Install() { ShowInstallButton = false; await DoInstall(); ShowInstallButton = true; } [RelayCommand] private Task ToggleAdvancedMode() { EventManager.Instance.OnOneClickInstallFinished(true); return Task.CompletedTask; } private async Task DoInstall() { HeaderText = $"Installing {SelectedPackage.DisplayName}..."; var progressHandler = new Progress(progress => { if (progress.Message != null && progress.Message.Contains("Downloading")) { SubHeaderText = $"Downloading prerequisites... {progress.Percentage:N0}%"; } else if (progress.Type == ProgressType.Extract) { SubHeaderText = $"Installing git... {progress.Percentage:N0}%"; } else if (progress.Title != null && progress.Title.Contains("Unpacking")) { SubHeaderText = $"Unpacking resources... {progress.Percentage:N0}%"; } else if (progress.Message != null) { SubHeaderText = progress.Message; } IsIndeterminate = progress.IsIndeterminate; OneClickInstallProgress = Convert.ToInt32(progress.Percentage); }); await prerequisiteHelper.InstallAllIfNecessary(progressHandler); SubHeaderText = "Installing prerequisites..."; IsIndeterminate = true; if (!PyRunner.PipInstalled) { await pyRunner.SetupPip(); } if (!PyRunner.VenvInstalled) { await pyRunner.InstallPackage("virtualenv"); } IsIndeterminate = false; // get latest version & download & install SubHeaderText = "Getting latest version..."; var latestVersion = await SelectedPackage.GetLatestVersion(); SelectedPackage.InstallLocation = $"{settingsManager.LibraryDir}\\Packages\\{SelectedPackage.Name}"; SelectedPackage.ConsoleOutput += (_, output) => SubSubHeaderText = output.Text; await DownloadPackage(latestVersion); await InstallPackage(); SubHeaderText = "Setting up shared folder links..."; sharedFolders.SetupLinksForPackage(SelectedPackage, SelectedPackage.InstallLocation); var installedPackage = new InstalledPackage { DisplayName = SelectedPackage.DisplayName, LibraryPath = $"Packages\\{SelectedPackage.Name}", Id = Guid.NewGuid(), PackageName = SelectedPackage.Name, PackageVersion = latestVersion, DisplayVersion = latestVersion, LaunchCommand = SelectedPackage.LaunchCommand, LastUpdateCheck = DateTimeOffset.Now }; await using var st = settingsManager.BeginTransaction(); st.Settings.InstalledPackages.Add(installedPackage); st.Settings.ActiveInstalledPackage = installedPackage.Id; EventManager.Instance.OnInstalledPackagesChanged(); HeaderText = "Installation complete!"; SubSubHeaderText = string.Empty; OneClickInstallProgress = 100; SubHeaderText = "Proceeding to Launch page in 3 seconds..."; await Task.Delay(1000); SubHeaderText = "Proceeding to Launch page in 2 seconds..."; await Task.Delay(1000); SubHeaderText = "Proceeding to Launch page in 1 second..."; await Task.Delay(1000); // should close dialog EventManager.Instance.OnOneClickInstallFinished(false); } private async Task DownloadPackage(string version) { SubHeaderText = "Downloading package..."; var progress = new Progress(progress => { IsIndeterminate = progress.IsIndeterminate; OneClickInstallProgress = Convert.ToInt32(progress.Percentage); EventManager.Instance.OnGlobalProgressChanged(OneClickInstallProgress); }); await SelectedPackage.DownloadPackage(version, false, progress); SubHeaderText = "Download Complete"; OneClickInstallProgress = 100; } private async Task InstallPackage() { SelectedPackage.ConsoleOutput += (_, output) => SubSubHeaderText = output.Text; SubHeaderText = "Downloading and installing package requirements..."; var progress = new Progress(progress => { IsIndeterminate = progress.IsIndeterminate; OneClickInstallProgress = Convert.ToInt32(progress.Percentage); EventManager.Instance.OnGlobalProgressChanged(OneClickInstallProgress); }); await SelectedPackage.InstallPackage(progress); } }