diff --git a/Avalonia.Gif/Avalonia.Gif.csproj b/Avalonia.Gif/Avalonia.Gif.csproj index 858b974e..6a363589 100644 --- a/Avalonia.Gif/Avalonia.Gif.csproj +++ b/Avalonia.Gif/Avalonia.Gif.csproj @@ -10,7 +10,7 @@ true - + diff --git a/CHANGELOG.md b/CHANGELOG.md index 69c61c94..c3acb129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,73 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +## v2.9.0 +### Added +- Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI +- Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel +- Added extension management for SD.Next and Stable Diffusion WebUI-UX +- Added the ability to choose where CivitAI model downloads are saved +- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`) +- Added more Base Model search options to the CivitAI Model Browser +- Added Stable Cascade to the HuggingFace Model Browser +#### Inference +- Added Inference Prompt Styles, with Prompt Expansion model support (i.e. Fooocus V2) +- Added option to load a .yaml config file next to the model with the same name. Can be used with VPred and other models that require a config file. +- Added copy image support on linux and macOS for Inference outputs viewer menu +### Changed +- Updated translations for German, Spanish, French, Japanese, Portuguese, and Turkish +- (Internal) Updated to Avalonia 11.0.9 +### Fixed +- Fixed StableSwarmUI not installing properly on macOS +- Fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS +- Fixed [#335](https://github.com/LykosAI/StabilityMatrix/issues/335) Update hanging indefinitely after git step for Auto1111 and SDWebUI Forge +- Fixed Inference output viewer menu "Copy" not copying image +- Fixed image viewer dialog arrow key navigation not working +- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins +- Fixed unknown model types not showing on checkpoints page (thanks Jerry!) +- Improved error handling for Inference Select Image hash calculation in case file is being written to while being read + +## v2.9.0-pre.2 +### Added +- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package "Stable Diffusion WebUI Forge"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`) +- Added more Base Model search options to the CivitAI Model Browser +- Added Stable Cascade to the HuggingFace Model Browser +### Changed +- (Internal) Updated to Avalonia 11.0.9 +### Fixed +- Fixed image viewer dialog arrow key navigation not working +- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins + +## v2.9.0-pre.1 +### Added +- Added Inference Prompt Styles, with Prompt Expansion model support (i.e. Fooocus V2) +- Added copy image support on linux and macOS for Inference outputs viewer menu +### Fixed +- Fixed StableSwarmUI not installing properly on macOS +- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS +- Fixed default command line args for SDWebUI Forge on macOS +- Fixed output paths and output sharing for SDWebUI Forge +- Maybe fixed update hanging for Auto1111 and SDWebUI Forge +- Fixed Inference output viewer menu "Copy" not copying image + +## v2.9.0-dev.2 +### Added +#### Inference +- Added option to load a .yaml config file next to the model with the same name. Can be used with VPred and other models that require a config file. +### Fixed +- Fixed icon sizes of Inference Addons and Steps buttons + +## v2.9.0-dev.1 +### Added +- Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI +- Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel +- Added extension management for SD.Next and Stable Diffusion WebUI-UX +- Added the ability to choose where CivitAI model downloads are saved + +## v2.8.4 +### Fixed +- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS + ## v2.8.3 ### Fixed - Fixed user tokens read error causing failed downloads diff --git a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj index e635ad2f..71efd8bb 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj +++ b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index ad48795c..1f039a8a 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -63,6 +63,7 @@ + @@ -77,6 +78,7 @@ + - + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml new file mode 100644 index 00000000..3332c1a5 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml @@ -0,0 +1,96 @@ + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs new file mode 100644 index 00000000..044410a2 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs @@ -0,0 +1,52 @@ +using AsyncAwaitBestPractices; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using FluentAvalonia.UI.Controls; +using StabilityMatrix.Avalonia.ViewModels.Inference; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models; + +namespace StabilityMatrix.Avalonia.Controls; + +[Transient] +public class PromptExpansionCard : TemplatedControl +{ + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + var upscalerComboBox = e.NameScope.Find("PART_ModelComboBox") as FAComboBox; + upscalerComboBox!.SelectionChanged += UpscalerComboBox_OnSelectionChanged; + } + + private void UpscalerComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (e.AddedItems.Count == 0) + return; + + var item = e.AddedItems[0]; + if (item is HybridModelFile { IsDownloadable: true }) + { + // Reset the selection + e.Handled = true; + + if ( + e.RemovedItems.Count > 0 + && e.RemovedItems[0] is HybridModelFile { IsDownloadable: false } removedItem + ) + { + (sender as FAComboBox)!.SelectedItem = removedItem; + } + else + { + (sender as FAComboBox)!.SelectedItem = null; + } + + // Show dialog to download the model + (DataContext as PromptExpansionCardViewModel)! + .RemoteDownloadCommand.ExecuteAsync(item) + .SafeFireAndForget(); + } + } +} diff --git a/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml index e931a603..6c59723d 100644 --- a/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml +++ b/StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml @@ -39,7 +39,7 @@ Padding="12,4" IsEnabled="{Binding !!Cards.Count}" IsChecked="{Binding IsEditEnabled}"> - + diff --git a/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml new file mode 100644 index 00000000..0e76024c --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml.cs b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml.cs new file mode 100644 index 00000000..8e01e487 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml.cs @@ -0,0 +1,135 @@ +using Avalonia; +using Avalonia.Controls.Primitives; +using StabilityMatrix.Avalonia.Models.TreeFileExplorer; +using StabilityMatrix.Core.Models.FileInterfaces; + +namespace StabilityMatrix.Avalonia.Controls; + +public class TreeFileExplorer : TemplatedControl +{ + public static readonly StyledProperty RootItemProperty = + AvaloniaProperty.Register("RootItem"); + + public TreeFileExplorerDirectory? RootItem + { + get => GetValue(RootItemProperty); + set => SetValue(RootItemProperty, value); + } + + public static readonly StyledProperty RootPathProperty = AvaloniaProperty.Register< + TreeFileExplorer, + string? + >("RootPath"); + + public string? RootPath + { + get => GetValue(RootPathProperty); + set => SetValue(RootPathProperty, value); + } + + public static readonly StyledProperty SelectedPathProperty = AvaloniaProperty.Register< + TreeFileExplorer, + IPathObject? + >("SelectedPath"); + + public IPathObject? SelectedPath + { + get => GetValue(SelectedPathProperty); + set => SetValue(SelectedPathProperty, value); + } + + public static readonly StyledProperty CanSelectFilesProperty = AvaloniaProperty.Register< + TreeFileExplorer, + bool + >("CanSelectFiles", true); + + public bool CanSelectFiles + { + get => GetValue(CanSelectFilesProperty); + set => SetValue(CanSelectFilesProperty, value); + } + + public static readonly StyledProperty CanSelectFoldersProperty = AvaloniaProperty.Register< + TreeFileExplorer, + bool + >("CanSelectFolders", true); + + public bool CanSelectFolders + { + get => GetValue(CanSelectFoldersProperty); + set => SetValue(CanSelectFoldersProperty, value); + } + + public static readonly StyledProperty IndexFilesProperty = AvaloniaProperty.Register< + TreeFileExplorer, + bool + >("IndexFiles", true); + + public bool IndexFiles + { + get => GetValue(IndexFilesProperty); + set => SetValue(IndexFilesProperty, value); + } + + public static readonly StyledProperty IndexFoldersProperty = AvaloniaProperty.Register< + TreeFileExplorer, + bool + >("IndexFolders", true); + + public bool IndexFolders + { + get => GetValue(IndexFoldersProperty); + set => SetValue(IndexFoldersProperty, value); + } + + private TreeFileExplorerOptions GetOptions() + { + var options = TreeFileExplorerOptions.None; + + if (CanSelectFiles) + { + options |= TreeFileExplorerOptions.CanSelectFiles; + } + if (CanSelectFolders) + { + options |= TreeFileExplorerOptions.CanSelectFolders; + } + if (IndexFiles) + { + options |= TreeFileExplorerOptions.IndexFiles; + } + if (IndexFolders) + { + options |= TreeFileExplorerOptions.IndexFolders; + } + + return options; + } + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + if (RootItem is null) + { + RootItem = RootPath is null + ? null + : new TreeFileExplorerDirectory(new DirectoryPath(RootPath), GetOptions()); + } + } + + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == RootPathProperty) + { + var path = change.GetNewValue(); + RootItem = path is null + ? null + : new TreeFileExplorerDirectory(new DirectoryPath(path), GetOptions()); + } + } +} diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 3f2476c9..77d5a384 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -192,7 +192,8 @@ public static class DesignData null, null, null, - null + null, + packageFactory ); ObservableCacheEx.AddOrUpdate( @@ -1007,6 +1008,8 @@ The gallery images are often inpainted, but you will get something very similar public static ControlNetCardViewModel ControlNetCardViewModel => DialogFactory.Get(); + public static string CurrentDirectory => Directory.GetCurrentDirectory(); + public static Indexer Types { get; } = new(); public class Indexer diff --git a/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs b/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs index 258c2602..debbe0f3 100644 --- a/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs +++ b/StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs @@ -27,6 +27,9 @@ public partial class MockInferenceClientManager : ObservableObject, IInferenceCl public IObservableCollection ControlNetModels { get; } = new ObservableCollectionExtended(); + public IObservableCollection PromptExpansionModels { get; } = + new ObservableCollectionExtended(); + public IObservableCollection Samplers { get; } = new ObservableCollectionExtended(ComfySampler.Defaults); @@ -64,28 +67,19 @@ public partial class MockInferenceClientManager : ObservableObject, IInferenceCl } /// - public Task CopyImageToInputAsync( - FilePath imageFile, - CancellationToken cancellationToken = default - ) + public Task CopyImageToInputAsync(FilePath imageFile, CancellationToken cancellationToken = default) { return Task.CompletedTask; } /// - public Task UploadInputImageAsync( - ImageSource image, - CancellationToken cancellationToken = default - ) + public Task UploadInputImageAsync(ImageSource image, CancellationToken cancellationToken = default) { return Task.CompletedTask; } /// - public Task WriteImageToInputAsync( - ImageSource imageSource, - CancellationToken cancellationToken = default - ) + public Task WriteImageToInputAsync(ImageSource imageSource, CancellationToken cancellationToken = default) { return Task.CompletedTask; } diff --git a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs index d60c7725..0c0fd05e 100644 --- a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs +++ b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs @@ -16,4 +16,28 @@ public static class IOCommands }, url => !string.IsNullOrWhiteSpace(url) ); + + public static AsyncRelayCommand OpenFileBrowserCommand { get; } = + new( + async path => + { + if (string.IsNullOrWhiteSpace(path)) + return; + + await ProcessRunner.OpenFileBrowser(path); + }, + path => !string.IsNullOrWhiteSpace(path) + ); + + public static AsyncRelayCommand OpenFolderBrowserCommand { get; } = + new( + async path => + { + if (string.IsNullOrWhiteSpace(path)) + return; + + await ProcessRunner.OpenFolderBrowser(path); + }, + path => !string.IsNullOrWhiteSpace(path) + ); } diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs index 296b1202..d31860db 100644 --- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.Versioning; @@ -43,6 +45,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 +92,28 @@ public class UnixPrerequisiteHelper( { await InstallNodeIfNecessary(progress); } + + if (prerequisites.Contains(PackagePrerequisite.Dotnet)) + { + await InstallDotnetIfNecessary(progress); + } + } + + public async Task InstallDotnetIfNecessary(IProgress? 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? progress = null) @@ -276,6 +312,7 @@ public class UnixPrerequisiteHelper( return ProcessRunner.RunBashCommand(args.Prepend("git").ToArray(), workingDirectory ?? ""); } + [Localizable(false)] [SupportedOSPlatform("Linux")] [SupportedOSPlatform("macOS")] public async Task RunNpm( @@ -285,27 +322,73 @@ public class UnixPrerequisiteHelper( IReadOnlyDictionary? envVars = null ) { - var command = args.Prepend([NpmPath]); + var process = ProcessRunner.StartAnsiProcess( + NpmPath, + args, + workingDirectory, + onProcessOutput, + envVars + ); - var result = await ProcessRunner.RunBashCommand(command.ToArray(), workingDirectory ?? "", envVars); - if (result.ExitCode != 0) - { - Logger.Error( - "npm command [{Command}] failed with exit code " + "{ExitCode}:\n{StdOut}\n{StdErr}", - command, - result.ExitCode, - result.StandardOutput, - result.StandardError - ); + await process.WaitForExitAsync(); - throw new ProcessException( - $"npm command [{command}] failed with exit code" - + $" {result.ExitCode}:\n{result.StandardOutput}\n{result.StandardError}" - ); - } + if (process.ExitCode == 0) + return; - onProcessOutput?.Invoke(ProcessOutput.FromStdOutLine(result.StandardOutput)); - onProcessOutput?.Invoke(ProcessOutput.FromStdErrLine(result.StandardError)); + var stdOut = await process.StandardOutput.ReadToEndAsync(); + var stdErr = await process.StandardError.ReadToEndAsync(); + + Logger.Error( + "RunNpm with args [{Args}] failed with exit code " + "{ExitCode}:\n{StdOut}\n{StdErr}", + args, + process.ExitCode, + stdOut, + stdErr + ); + + throw new ProcessException( + $"RunNpm with args [{args}] failed with exit code" + $" {process.ExitCode}:\n{stdOut}\n{stdErr}" + ); + } + + [SupportedOSPlatform("Linux")] + [SupportedOSPlatform("macOS")] + public async Task RunDotnet( + ProcessArgs args, + string? workingDirectory = null, + Action? onProcessOutput = null, + IReadOnlyDictionary? 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")] @@ -353,6 +436,40 @@ public class UnixPrerequisiteHelper( File.Delete(nodeDownloadPath); } + private async Task DownloadAndExtractPrerequisite( + IProgress? 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? progress = null) diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index 18c33143..d0b5b2cc 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/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? progress = null) { if (File.Exists(NodeExistsPath)) - { - Logger.Info("node already installed"); return; + + await DownloadAndExtractPrerequisite(progress, NodeDownloadUrl, NodeDownloadPath, AssetsDir); + + var extractedNodeDir = Path.Combine(AssetsDir, "node-v20.11.0-win-x64"); + if (Directory.Exists(extractedNodeDir)) + { + Directory.Move(extractedNodeDir, Path.Combine(AssetsDir, "nodejs")); } + } + + [SupportedOSPlatform("windows")] + public async Task InstallDotnetIfNecessary(IProgress? progress = null) + { + if (File.Exists(DotnetExistsPath)) + return; + + await DownloadAndExtractPrerequisite( + progress, + Dotnet7DownloadUrl, + Dotnet7DownloadPath, + DotnetExtractPath + ); + await DownloadAndExtractPrerequisite( + progress, + Dotnet8DownloadUrl, + Dotnet8DownloadPath, + DotnetExtractPath + ); + } + + public async Task RunDotnet( + ProcessArgs args, + string? workingDirectory = null, + Action? onProcessOutput = null, + IReadOnlyDictionary? envVars = null, + bool waitForExit = true + ) + { + var process = ProcessRunner.StartAnsiProcess( + DotnetExistsPath, + args, + workingDirectory, + onProcessOutput, + envVars + ); + + if (!waitForExit) + return process; - Logger.Info("Downloading node"); - await downloadService.DownloadToFileAsync(NodeDownloadUrl, NodeDownloadPath, progress: progress); + 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 + ); - Logger.Info("Installing node"); + throw new ProcessException( + $"dotnet8 with args [{args}] failed with exit code" + + $" {process.ExitCode}:\n{process.StandardOutput}\n{process.StandardError}" + ); + } + + private async Task DownloadAndExtractPrerequisite( + IProgress? progress, + string downloadUrl, + string downloadPath, + string extractPath + ) + { + Logger.Info($"Downloading {downloadUrl} to {downloadPath}"); + await downloadService.DownloadToFileAsync(downloadUrl, downloadPath, progress: progress); + + 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? progress = null) diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs index 2ddf7c05..0538cae2 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs +++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs @@ -644,6 +644,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Please select a download location.. + /// + public static string Error_PleaseSelectDownloadLocation { + get { + return ResourceManager.GetString("Error_PleaseSelectDownloadLocation", resourceCulture); + } + } + /// /// Looks up a localized string similar to About. /// @@ -887,6 +896,24 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to ComfyUI is required to install this package. Would you like to install it now?. + /// + public static string Label_ComfyRequiredDetail { + get { + return ResourceManager.GetString("Label_ComfyRequiredDetail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ComfyUI Required. + /// + public static string Label_ComfyRequiredTitle { + get { + return ResourceManager.GetString("Label_ComfyRequiredTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Comments. /// @@ -914,6 +941,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Config. + /// + public static string Label_Config { + get { + return ResourceManager.GetString("Label_Config", resourceCulture); + } + } + /// /// Looks up a localized string similar to Confirm Delete. /// @@ -1653,7 +1689,7 @@ namespace StabilityMatrix.Avalonia.Languages { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to None. /// public static string Label_NotificationOption_None { get { @@ -2021,6 +2057,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Select Download Location:. + /// + public static string Label_SelectDownloadLocation { + get { + return ResourceManager.GetString("Label_SelectDownloadLocation", resourceCulture); + } + } + /// /// Looks up a localized string similar to Select new Data Directory. /// diff --git a/StabilityMatrix.Avalonia/Languages/Resources.de.resx b/StabilityMatrix.Avalonia/Languages/Resources.de.resx index 1821cda1..1a2ef15f 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.de.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.de.resx @@ -521,6 +521,7 @@ Wählen Sie Ihre bevorzugte Schnittstelle und klicken Sie auf Installieren, um loszulegen. + Fuzzy Installiert @@ -777,6 +778,9 @@ API Schlüssel + + Accounts + Preprocessor diff --git a/StabilityMatrix.Avalonia/Languages/Resources.es.resx b/StabilityMatrix.Avalonia/Languages/Resources.es.resx index 4dccbf2a..5efb7a15 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.es.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.es.resx @@ -522,6 +522,7 @@ Elige tu interfaz preferida y pulsa Instalar para comenzar + Fuzzy Instalando @@ -782,4 +783,184 @@ Cuentas + + Preprocesador + + + Fortaleza + + + Control de Peso + + + Control de Pasos + + + Debes iniciar sesión para descargar este checkpoint. Ingresa una clave API de CivitAI en la configuración. + + + Descarga Fallida + + + Actualizaciones automáticas + + + Para los usuarios primerizos. Las versiones preliminares serán más fiables que las del canal de desarrollo y estarán disponibles antes de las versiones estables. Tus comentarios nos ayudarán enormemente a descubrir problemas y pulir elementos de diseño. + + + Para usuarios técnicos. Sé el primero en acceder a nuestras compilaciones de desarrollo desde las ramas de funciones tan pronto como estén disponibles. Es posible que haya algunas asperezas y errores a medida que experimentamos con nuevas funciones. + + + Actualizaciones + + + Estás al día + + + Última comprobación: {0} + + + Copiar palabras desencadenantes + + + Palabras desencadenantes: + + + Aquí se pueden habilitar carpetas adicionales como IPAdapters y TextualInversions (embeddings) + + + Abrir en Hugging Face + + + Actualizar metadatos existentes + + + General + A general settings category + + + Inferencia + The Inference feature page + + + Prompt + A settings category for Inference generation prompts + + + Archivos de imágenes resultantes + + + Visor de Imágenes + + + Auto Completado + + + Reemplazar guiones bajos con espacios al insertar terminaciones + + + Etiquetas de Prompt + Tags for image generation prompts + + + Importar Etiquetas de Prompt + + + Archivo de etiquetas que se utilizarán para sugerir terminaciones (admite el formato .csv a1111-sd-webui-tagcomplete) + + + Información del Sistema + + + CivitAI + + + Hugging Face + + + Complementos + Inference Sampler Addons + + + Guardar Imagen Intermedia + Inference module step to save an intermediate image + + + Ajustes + + + Seleccionar Archivo + + + Reemplazar contenido + + + No disponible aún + + + La función estará disponible en una actualización futura + + + Archivo de Imagen Faltante + + + Modo Vacaciones + + + Saltar CLIP + + + Imagen a Vídeo + + + Frames por Segundo + + + CFG Mínimo + + + Sin pérdidas + + + Frames + + + Motion Bucket ID + + + Nivel de aumento + + + Método + + + Calidad + + + Buscar en el navegador de modelos + + + Instalado + + + No se encuentran extensiones. + + + Ocultar + + + Copiar detalles + + + Descargar + + + Verifica el progreso de instalación de tus paquetes y descargas de modelos aquí. + + + Modelos recomendados + + + Mientras se instala tu paquete, aquí hay algunos modelos que recomendamos para ayudarte a comenzar. + \ No newline at end of file diff --git a/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx b/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx index b8eee78d..6e195f4d 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx @@ -319,7 +319,7 @@ Recherche - Trier + Tri Période @@ -810,6 +810,9 @@ Dernière vérification: {0} + + Les dossiers additionnels comme IPAdapters ou TextualInversions (embeddings) peuvent être activés ici + Ouvrir sur Hugging Face @@ -834,6 +837,9 @@ Auto-complétion + + Remplacer les underscores par des espaces lors de l'auto-complétion. + Informations système @@ -888,10 +894,40 @@ Motion Bucket ID + + Niveau d'augmentation + Méthode Qualité + + Trouver dans l'explorateur de modèle + + + Installé + + + Pas d'extensions trouvés. + + + Masquer + + + Copier les détails + + + Télécharger + + + Vérifiez la progression de l'installation de votre paquet ainsi que le téléchargement de votre modèle ici. + + + Modèles recommandés + + + Pendant que votre paquet s'installe, voici quelques modèles que nous recommandons pour vous aidez à démarrer. + \ No newline at end of file diff --git a/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx b/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx index 9449fc9f..3187678c 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx @@ -530,6 +530,7 @@ 好きなUIをインストールして始めよう + Fuzzy インストール中 @@ -706,6 +707,24 @@ 更新 + + GitHubで開く + + + ユーザ名 + + + パスワード + + + ログイン + + + サインアップ + + + パスワード再確認 + ダウンロードにはCivitAIのログインが必要です。SettingからAPIキーを入力してください。 @@ -715,6 +734,9 @@ Devビルドはテクニカルなユーザ向けです。新機能をいち早く利用できます。荒削りな部分やバグがあるかもしれません。 + + IPAdaptersやTextualInversions(embeddings)などのフォルダは、ここで有効にできます。 + Hugging Faceで開く @@ -734,4 +756,13 @@ 非圧縮 + + Packageのインストール・Modelのダウンロードの進捗状況はこちらで確認できます。 + + + 推奨Model + + + Packageのインストール中に、推奨Modelをいくつかご紹介しましょう。 + \ No newline at end of file diff --git a/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx b/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx index 9c208fff..c74bd016 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx @@ -196,7 +196,7 @@ Passos - Refinamento - Escale de CFG + Escala de CFG Nível de Denoising @@ -391,7 +391,7 @@ Pasta de Modelos - Catagorias + Categorias Vamos começar! @@ -409,7 +409,7 @@ Exibir imagens dos Modelos - Aparencia + Aparência Tema @@ -521,6 +521,7 @@ Escolha sua interface preferida e clique em Instalar para começar + Fuzzy Instalando @@ -840,7 +841,7 @@ The Inference feature page - Incitar + Prompt A settings category for Inference generation prompts @@ -853,17 +854,17 @@ Preenchimento automático - Substitua sublinhados por espaços ao inserir conclusões + Substitua caracteres sublinhados por espaços ao inserir conclusões - Etiquetas de prompt + Etiquetas de Prompt Tags for image generation prompts - Tags de prompt de importação + Importar Tags de Prompt - Arquivo de tags a ser usado para sugerir conclusões (suporta o formato .csv a1111-sd-webui-tagcomplete) + Arquivo de tags a ser usado para Sugestão de Completions (suporta o formato .csv a1111-sd-webui-tagcomplete) Informação do sistema @@ -872,7 +873,7 @@ CivitAI - Abraçando o rosto + Hugging Face Complementos @@ -901,7 +902,7 @@ Arquivo de imagem não encontrado - Modo Férias + Modo Feriado Pular CLIPE @@ -930,4 +931,7 @@ Método + + Qualidade + \ No newline at end of file diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx index 4b038c76..4f15299e 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.resx @@ -946,7 +946,7 @@ Notifications - + None Download @@ -960,4 +960,19 @@ While your package is installing, here are some models we recommend to help you get started. + + ComfyUI Required + + + ComfyUI is required to install this package. Would you like to install it now? + + + Please select a download location. + + + Select Download Location: + + + Config + diff --git a/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx b/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx index 603f6feb..8d2e1d7f 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx @@ -133,7 +133,7 @@ Dil - Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekiyor + Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekir. Yeniden başlat @@ -166,7 +166,7 @@ Dallar - İçe aktarmak için chekpoints'leri buraya sürükleyip bırakın + İçe aktarmak için chekpoints'leri buraya sürükleyip bırakın. Vurgu @@ -235,7 +235,7 @@ Sponsor Ol - Discord Sunucusuna Katıl + Discord Sunucumuza Katıl İndirmeler @@ -313,7 +313,7 @@ Tüm Sürümler - Modelleri, #etiketleri veya @kullanıcıları ara + Modelleri,Tagları (#tag) veya Kullanıcıları (@user) Buradan Arayabilirsin Ara @@ -322,13 +322,13 @@ Sırala - Süre + Sıralama Tarihi Model Türü - Temel Model + Model Altyapısı NSFW İçerik Göster @@ -376,10 +376,10 @@ Klasör - İçe aktarma için dosyayı buraya bırakın + Dosyayı içeri aktarmak için lütfen sürükleyip işaretlenen alana bırakın. - Metadata ile içeri aktar + Dosyaları metadata ile içeri aktar Yeni yerel içe aktarmalar için bağlı meta veri arayın @@ -448,7 +448,7 @@ Entegrasyonlar - Discord Zengin Varlık + Discord Etkinliğini StabilityMatrix Olarak Göster. Sistem @@ -469,10 +469,10 @@ Tüm Kullanıcılar İçin Ekle - Yeni Veri Dizini Seç + Yeni Dosya Dizinini Seç - Mevcut veriyi taşımaz + Bu işlem mevcut veriyi yeni dizine taşımaz. Dizin Seçin @@ -490,7 +490,7 @@ Başlamak için Başlat'a tıklayın! - Dur + Durdur Giriş Gönder @@ -521,6 +521,7 @@ Tercih ettiğiniz arayüzü seçin ve başlamak için Yükle'ye tıklayın + Fuzzy Yükleniyor @@ -694,10 +695,10 @@ {0} resim seçildi - Çıkış Klasörü + Çıktı Klasörü - Çıkış Türü + Çıktı Türü Seçimi Temizle @@ -721,7 +722,7 @@ Upscale - Çıkış Tarayıcısı + Medya Kitaplığı 1 resim seçildi @@ -933,4 +934,31 @@ Kalite + + Model Tarayıcıda Bul + + + Kurulmuş + + + Uzantı bulunamadı. + + + Gizle + + + Ayrıntıları Kopyala + + + İndir + + + Paket kurulumlarınızın ve model indirmelerinizin ilerlemesini buradan kontrol edin. + + + Önerilen Modeller + + + Paketiniz yüklenirken başlamanıza yardımcı olması için önerdiğimiz bazı modeller aşağıda verilmiştir. + \ No newline at end of file diff --git a/StabilityMatrix.Avalonia/Models/AppArgs.cs b/StabilityMatrix.Avalonia/Models/AppArgs.cs index b1e72bf9..7f836b11 100644 --- a/StabilityMatrix.Avalonia/Models/AppArgs.cs +++ b/StabilityMatrix.Avalonia/Models/AppArgs.cs @@ -77,6 +77,13 @@ public class AppArgs [Option("data-dir", HelpText = "Override data directory")] public string? DataDirectoryOverride { get; set; } + /// + /// Launch an installed package on startup + /// Can use package ID or name + /// + [Option("launch-package", HelpText = "Package ID or name to launch on startup")] + public string? LaunchPackageName { get; set; } + /// /// Custom Uri protocol handler /// This will send the Uri to the running instance of the app via IPC and exit diff --git a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs index 8119abc1..375ca17f 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; -using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; +using System.IO; +using System.IO.Hashing; +using System.Text; using StabilityMatrix.Core.Models.Api.Comfy.Nodes; +using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; namespace StabilityMatrix.Avalonia.Models.Inference; @@ -17,19 +20,40 @@ public class ModuleApplyStepEventArgs : EventArgs public ModuleApplyStepTemporaryArgs Temp { get; } = new(); /// - /// Index of the step in the pipeline. + /// Generation overrides (like hires fix generate, current seed generate, etc.) /// - public int StepIndex { get; init; } + public IReadOnlyDictionary IsEnabledOverrides { get; init; } = new Dictionary(); - /// - /// Index - /// - public int StepTypeIndex { get; init; } + public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = []; + + public void AddFileTransfer(string sourcePath, string destinationRelativePath) + { + FilesToTransfer.Add((sourcePath, destinationRelativePath)); + } /// - /// Generation overrides (like hires fix generate, current seed generate, etc.) + /// Adds a file transfer to `models/configs` /// - public IReadOnlyDictionary IsEnabledOverrides { get; init; } = new Dictionary(); + /// The destination relative path + public string AddFileTransferToConfigs(string sourcePath) + { + // To avoid conflicts, we'll add the file name's crc32 before the extension + var sourceNameWithoutExtension = Path.GetFileNameWithoutExtension(sourcePath); + var sourceExtension = Path.GetExtension(sourcePath); + var sourceNameCrc = Crc32.Hash(Encoding.UTF8.GetBytes(sourceNameWithoutExtension)); + var sourceNameCrcShort = BitConverter + .ToString(sourceNameCrc) + .ToLowerInvariant() + .Replace("-", string.Empty)[..4]; + + var destFileName = $"{sourceNameWithoutExtension}_{sourceNameCrcShort}{sourceExtension}"; + + var destPath = Path.Combine("models", "configs", destFileName); + + FilesToTransfer.Add((sourcePath, destPath)); + + return destPath; + } public class ModuleApplyStepTemporaryArgs { diff --git a/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs b/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs index 22029c30..cc088a94 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs @@ -1,10 +1,10 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Nodes; namespace StabilityMatrix.Avalonia.Models.Inference; -[JsonSerializable(typeof(PromptCardModel))] public class PromptCardModel { - public string? Prompt { get; set; } - public string? NegativePrompt { get; set; } + public string? Prompt { get; init; } + public string? NegativePrompt { get; init; } + public JsonObject? ModulesCardState { get; init; } } diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerDirectory.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerDirectory.cs new file mode 100644 index 00000000..f5c19ed0 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerDirectory.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StabilityMatrix.Core.Models.FileInterfaces; + +namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer; + +public class TreeFileExplorerDirectory(IPathObject path, TreeFileExplorerOptions options) + : TreeFileExplorerItem(path, options) +{ + public IEnumerable Children => + GetChildren(Path, Options) + .OrderByDescending(item => item.Path is DirectoryPath) + .ThenBy(item => item.Path.Name); + + private static IEnumerable GetChildren( + IPathObject pathObject, + TreeFileExplorerOptions options + ) + { + return pathObject switch + { + FilePath => Enumerable.Empty(), + DirectoryPath directoryPath => GetChildren(directoryPath, options), + _ => throw new NotSupportedException() + }; + } + + private static IEnumerable GetChildren( + DirectoryPath directoryPath, + TreeFileExplorerOptions options + ) + { + if (options.HasFlag(TreeFileExplorerOptions.IndexFiles)) + { + foreach (var file in directoryPath.EnumerateFiles()) + { + yield return new TreeFileExplorerFile(file, options); + } + } + + if (options.HasFlag(TreeFileExplorerOptions.IndexFolders)) + { + foreach (var directory in directoryPath.EnumerateDirectories()) + { + yield return new TreeFileExplorerDirectory(directory, options); + } + } + } +} diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerFile.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerFile.cs new file mode 100644 index 00000000..db793f67 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerFile.cs @@ -0,0 +1,6 @@ +using StabilityMatrix.Core.Models.FileInterfaces; + +namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer; + +public class TreeFileExplorerFile(IPathObject path, TreeFileExplorerOptions options) + : TreeFileExplorerItem(path, options); diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerItem.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerItem.cs new file mode 100644 index 00000000..c0b49dc4 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerItem.cs @@ -0,0 +1,10 @@ +using StabilityMatrix.Core.Models.FileInterfaces; + +namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer; + +public class TreeFileExplorerItem(IPathObject path, TreeFileExplorerOptions options) +{ + public IPathObject Path { get; } = path; + + public TreeFileExplorerOptions Options { get; } = options; +} diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerOptions.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerOptions.cs new file mode 100644 index 00000000..1afd274d --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerOptions.cs @@ -0,0 +1,15 @@ +using System; + +namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer; + +[Flags] +public enum TreeFileExplorerOptions +{ + None = 0, + + IndexFiles = 1 << 5, + IndexFolders = 1 << 6, + + CanSelectFiles = 1 << 10, + CanSelectFolders = 1 << 11, +} diff --git a/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerType.cs b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerType.cs new file mode 100644 index 00000000..801d18e8 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerType.cs @@ -0,0 +1,8 @@ +namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer; + +public enum TreeFileExplorerType +{ + None, + File, + Directory +} diff --git a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs index b0d8fb13..fc134c74 100644 --- a/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs +++ b/StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs @@ -12,10 +12,7 @@ using StabilityMatrix.Core.Models.FileInterfaces; namespace StabilityMatrix.Avalonia.Services; -public interface IInferenceClientManager - : IDisposable, - INotifyPropertyChanged, - INotifyPropertyChanging +public interface IInferenceClientManager : IDisposable, INotifyPropertyChanged, INotifyPropertyChanging { ComfyClient? Client { get; set; } @@ -43,6 +40,7 @@ public interface IInferenceClientManager IObservableCollection Models { get; } IObservableCollection VaeModels { get; } IObservableCollection ControlNetModels { get; } + IObservableCollection PromptExpansionModels { get; } IObservableCollection Samplers { get; } IObservableCollection Upscalers { get; } IObservableCollection Schedulers { get; } @@ -51,10 +49,7 @@ public interface IInferenceClientManager Task UploadInputImageAsync(ImageSource image, CancellationToken cancellationToken = default); - Task WriteImageToInputAsync( - ImageSource imageSource, - CancellationToken cancellationToken = default - ); + Task WriteImageToInputAsync(ImageSource imageSource, CancellationToken cancellationToken = default); Task ConnectAsync(CancellationToken cancellationToken = default); diff --git a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs index b3afcdf2..5ad092af 100644 --- a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs +++ b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs @@ -74,6 +74,14 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient public IObservableCollection ControlNetModels { get; } = new ObservableCollectionExtended(); + private readonly SourceCache promptExpansionModelsSource = new(p => p.GetId()); + + private readonly SourceCache downloadablePromptExpansionModelsSource = + new(p => p.GetId()); + + public IObservableCollection PromptExpansionModels { get; } = + new ObservableCollectionExtended(); + private readonly SourceCache samplersSource = new(p => p.Name); public IObservableCollection Samplers { get; } = @@ -130,6 +138,18 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient .Bind(ControlNetModels) .Subscribe(); + promptExpansionModelsSource + .Connect() + .Or(downloadablePromptExpansionModelsSource.Connect()) + .Sort( + SortExpressionComparer + .Ascending(f => f.Type) + .ThenByAscending(f => f.ShortDisplayName) + ) + .DeferUntilLoaded() + .Bind(PromptExpansionModels) + .Subscribe(); + vaeModelsDefaults.AddOrUpdate(HybridModelFile.Default); vaeModelsDefaults.Connect().Or(vaeModelsSource.Connect()).Bind(VaeModels).Subscribe(); @@ -199,6 +219,8 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient ); } + // Prompt Expansion indexing is local only + // Fetch sampler names from KSampler node if (await Client.GetSamplerNamesAsync() is { } samplerNames) { @@ -277,6 +299,22 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient ); downloadableControlNetModelsSource.EditDiff(downloadableControlNets, HybridModelFile.Comparer); + // Load local prompt expansion models + promptExpansionModelsSource.EditDiff( + modelIndexService + .GetFromModelIndex(SharedFolderType.PromptExpansion) + .Select(HybridModelFile.FromLocal), + HybridModelFile.Comparer + ); + + // Downloadable PromptExpansion models + downloadablePromptExpansionModelsSource.EditDiff( + RemoteModels.PromptExpansionModels.Where( + u => !promptExpansionModelsSource.Lookup(u.GetId()).HasValue + ), + HybridModelFile.Comparer + ); + // Load local VAE models vaeModelsSource.EditDiff( modelIndexService.GetFromModelIndex(SharedFolderType.VAE).Select(HybridModelFile.FromLocal), @@ -369,6 +407,7 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient inferenceInputs.Create(); } + [MemberNotNull(nameof(Client))] private async Task ConnectAsyncImpl(Uri uri, CancellationToken cancellationToken = default) { if (IsConnected) @@ -480,11 +519,8 @@ public partial class InferenceClientManager : ObservableObject, IInferenceClient await ConnectAsyncImpl(uri, cancellationToken); - var packageDir = new DirectoryPath(packagePair.InstalledPackage.FullPath); - - // Set package paths - Client!.OutputImagesDir = packageDir.JoinDir("output"); - Client!.InputImagesDir = packageDir.JoinDir("input"); + Client.LocalServerPackage = packagePair; + Client.LocalServerPath = packagePair.InstalledPackage.FullPath!; } public async Task CloseAsync() diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj index 1190f686..4929efc4 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj @@ -17,7 +17,7 @@ app.manifest true ./Assets/Icon.ico - 2.9.0-dev.999 + 2.10.0-dev.999 $(Version) true true @@ -39,13 +39,13 @@ - + - - - + + + - + @@ -65,8 +65,8 @@ - - + + @@ -77,23 +77,26 @@ - + - + + - + - - + + - - + + + + diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings index 78669f79..ef254925 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings @@ -1,3 +1,5 @@  + Yes + Pessimistic UI True diff --git a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml index 7565401b..61d77d82 100644 --- a/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml +++ b/StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml @@ -10,7 +10,10 @@ - + - + - + + + + + + @@ -236,7 +261,7 @@ - +