From 49273b049a143bb4ba8d5979b9b9ea3bf6db1f62 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 25 Feb 2024 16:54:25 -0800 Subject: [PATCH] wip start of multi-package running stuff --- .../Services/RunningPackageService.cs | 135 ++++++++++++++++++ .../PackageManager/PackageCardViewModel.cs | 54 ++++--- .../PackageInstallDetailViewModel.cs | 2 +- .../ViewModels/RunningPackageViewModel.cs | 13 ++ .../Views/ConsoleOutputPage.axaml | 22 +++ .../Views/ConsoleOutputPage.axaml.cs | 13 ++ .../Helper/Factory/IPackageFactory.cs | 1 + .../Helper/Factory/PackageFactory.cs | 33 ++++- 8 files changed, 241 insertions(+), 32 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Services/RunningPackageService.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs create mode 100644 StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml create mode 100644 StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs diff --git a/StabilityMatrix.Avalonia/Services/RunningPackageService.cs b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs new file mode 100644 index 00000000..d112a71f --- /dev/null +++ b/StabilityMatrix.Avalonia/Services/RunningPackageService.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Controls.Notifications; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.Extensions.Logging; +using StabilityMatrix.Avalonia.Models; +using StabilityMatrix.Avalonia.ViewModels; +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.FileInterfaces; +using StabilityMatrix.Core.Models.Packages; +using StabilityMatrix.Core.Python; +using StabilityMatrix.Core.Services; + +namespace StabilityMatrix.Avalonia.Services; + +[Singleton] +public partial class RunningPackageService( + ILogger logger, + IPackageFactory packageFactory, + INotificationService notificationService, + ISettingsManager settingsManager, + IPyRunner pyRunner +) : ObservableObject +{ + [ObservableProperty] + private ObservableDictionary runningPackages = []; + + public async Task StartPackage(InstalledPackage installedPackage, string? command = null) + { + var activeInstallName = installedPackage.PackageName; + var basePackage = string.IsNullOrWhiteSpace(activeInstallName) + ? null + : packageFactory.GetNewBasePackage(installedPackage); + + if (basePackage == null) + { + logger.LogWarning( + "During launch, package name '{PackageName}' did not match a definition", + activeInstallName + ); + + notificationService.Show( + new Notification( + "Package name invalid", + "Install package name did not match a definition. Please reinstall and let us know about this issue.", + NotificationType.Error + ) + ); + return null; + } + + // If this is the first launch (LaunchArgs is null), + // load and save a launch options dialog vm + // so that dynamic initial values are saved. + if (installedPackage.LaunchArgs == null) + { + var definitions = basePackage.LaunchOptions; + // Create config cards and save them + var cards = LaunchOptionCard + .FromDefinitions(definitions, Array.Empty()) + .ToImmutableArray(); + + var args = cards.SelectMany(c => c.Options).ToList(); + + logger.LogDebug( + "Setting initial launch args: {Args}", + string.Join(", ", args.Select(o => o.ToArgString()?.ToRepr())) + ); + + settingsManager.SaveLaunchArgs(installedPackage.Id, args); + } + + if (basePackage is not StableSwarm) + { + await pyRunner.Initialize(); + } + + // Get path from package + var packagePath = new DirectoryPath(settingsManager.LibraryDir, installedPackage.LibraryPath!); + + if (basePackage is not StableSwarm) + { + // Unpack sitecustomize.py to venv + await UnpackSiteCustomize(packagePath.JoinDir("venv")); + } + + // Clear console and start update processing + var console = new ConsoleViewModel(); + console.StartUpdates(); + + // Update shared folder links (in case library paths changed) + await basePackage.UpdateModelFolders( + packagePath, + installedPackage.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod + ); + + // Load user launch args from settings and convert to string + var userArgs = installedPackage.LaunchArgs ?? []; + var userArgsString = string.Join(" ", userArgs.Select(opt => opt.ToArgString())); + + // Join with extras, if any + userArgsString = string.Join(" ", userArgsString, basePackage.ExtraLaunchArguments); + + // Use input command if provided, otherwise use package launch command + command ??= basePackage.LaunchCommand; + + await basePackage.RunPackage(packagePath, command, userArgsString, o => console.Post(o)); + var runningPackage = new PackagePair(installedPackage, basePackage); + + EventManager.Instance.OnRunningPackageStatusChanged(runningPackage); + + var viewModel = new RunningPackageViewModel(runningPackage, console); + RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel); + + return runningPackage.InstalledPackage.Id; + } + + public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) => + RunningPackages.TryGetValue(id, out var vm) ? vm : null; + + private static async Task UnpackSiteCustomize(DirectoryPath venvPath) + { + var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath); + var file = sitePackages.JoinFile("sitecustomize.py"); + file.Directory?.Create(); + await Assets.PyScriptSiteCustomize.ExtractTo(file, true); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index ba815fbf..20f97b72 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -26,7 +26,6 @@ using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.Packages; -using StabilityMatrix.Core.Models.Settings; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Services; @@ -34,15 +33,16 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager; [ManagedService] [Transient] -public partial class PackageCardViewModel : ProgressViewModel +public partial class PackageCardViewModel( + ILogger logger, + IPackageFactory packageFactory, + INotificationService notificationService, + ISettingsManager settingsManager, + INavigationService navigationService, + ServiceManager vmFactory, + RunningPackageService runningPackageService +) : ProgressViewModel { - private readonly ILogger logger; - private readonly IPackageFactory packageFactory; - private readonly INotificationService notificationService; - private readonly ISettingsManager settingsManager; - private readonly INavigationService navigationService; - private readonly ServiceManager vmFactory; - [ObservableProperty] private InstalledPackage? package; @@ -82,23 +82,6 @@ public partial class PackageCardViewModel : ProgressViewModel [ObservableProperty] private bool canUseExtensions; - public PackageCardViewModel( - ILogger logger, - IPackageFactory packageFactory, - INotificationService notificationService, - ISettingsManager settingsManager, - INavigationService navigationService, - ServiceManager vmFactory - ) - { - this.logger = logger; - this.packageFactory = packageFactory; - this.notificationService = notificationService; - this.settingsManager = settingsManager; - this.navigationService = navigationService; - this.vmFactory = vmFactory; - } - partial void OnPackageChanged(InstalledPackage? value) { if (string.IsNullOrWhiteSpace(value?.PackageName)) @@ -163,15 +146,26 @@ public partial class PackageCardViewModel : ProgressViewModel } } - public void Launch() + public async Task Launch() { if (Package == null) return; - settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id); + var packageId = await runningPackageService.StartPackage(Package); + + if (packageId != null) + { + var vm = runningPackageService.GetRunningPackageViewModel(packageId.Value); + if (vm != null) + { + navigationService.NavigateTo(vm, new BetterDrillInNavigationTransition()); + } + } - navigationService.NavigateTo(new BetterDrillInNavigationTransition()); - EventManager.Instance.OnPackageLaunchRequested(Package.Id); + // settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id); + // + // navigationService.NavigateTo(new BetterDrillInNavigationTransition()); + // EventManager.Instance.OnPackageLaunchRequested(Package.Id); } public async Task Uninstall() diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs index 71e14b31..9172d5c5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs @@ -281,7 +281,7 @@ public partial class PackageInstallDetailViewModel( SelectedVersion = !IsReleaseMode ? AvailableVersions?.FirstOrDefault(x => x.TagName == SelectedPackage.MainBranch) ?? AvailableVersions?.FirstOrDefault() - : AvailableVersions?.FirstOrDefault(); + : AvailableVersions?.FirstOrDefault(v => !v.IsPrerelease); CanInstall = !ShowDuplicateWarning; } diff --git a/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs new file mode 100644 index 00000000..d2beffe6 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Avalonia.Views; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models; + +namespace StabilityMatrix.Avalonia.ViewModels; + +[View(typeof(ConsoleOutputPage))] +public class RunningPackageViewModel(PackagePair runningPackage, ConsoleViewModel console) : ViewModelBase +{ + public PackagePair RunningPackage { get; } = runningPackage; + public ConsoleViewModel Console { get; } = console; +} diff --git a/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml new file mode 100644 index 00000000..8d8bdbc2 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml @@ -0,0 +1,22 @@ + + + diff --git a/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs new file mode 100644 index 00000000..1c7fdae6 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views; + +[Transient] +public partial class ConsoleOutputPage : UserControlBase +{ + public ConsoleOutputPage() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs index b088c3bf..ccd855f8 100644 --- a/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs +++ b/StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs @@ -10,4 +10,5 @@ public interface IPackageFactory BasePackage? this[string packageName] { get; } PackagePair? GetPackagePair(InstalledPackage? installedPackage); IEnumerable GetPackagesByType(PackageType packageType); + BasePackage GetNewBasePackage(InstalledPackage installedPackage); } diff --git a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs index a31bca2b..0e959e89 100644 --- a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs +++ b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs @@ -1,22 +1,53 @@ using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Packages; +using StabilityMatrix.Core.Services; namespace StabilityMatrix.Core.Helper.Factory; [Singleton(typeof(IPackageFactory))] public class PackageFactory : IPackageFactory { + private readonly IGithubApiCache githubApiCache; + private readonly ISettingsManager settingsManager; + private readonly IDownloadService downloadService; + private readonly IPrerequisiteHelper prerequisiteHelper; + /// /// Mapping of package.Name to package /// private readonly Dictionary basePackages; - public PackageFactory(IEnumerable basePackages) + public PackageFactory( + IEnumerable basePackages, + IGithubApiCache githubApiCache, + ISettingsManager settingsManager, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper + ) { + this.githubApiCache = githubApiCache; + this.settingsManager = settingsManager; + this.downloadService = downloadService; + this.prerequisiteHelper = prerequisiteHelper; this.basePackages = basePackages.ToDictionary(x => x.Name); } + public BasePackage GetNewBasePackage(InstalledPackage installedPackage) + { + return installedPackage.PackageName switch + { + "ComfyUI" => new ComfyUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "Fooocus" => new Fooocus(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "stable-diffusion-webui" + => new A3WebUI(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + "Fooocus-ControlNet-SDXL" + => new FocusControlNet(githubApiCache, settingsManager, downloadService, prerequisiteHelper), + _ => throw new ArgumentOutOfRangeException() + }; + } + public IEnumerable GetAllAvailablePackages() { return basePackages.Values.OrderBy(p => p.InstallerSortOrder).ThenBy(p => p.DisplayName);