JT
8 months ago
committed by
GitHub
73 changed files with 3341 additions and 846 deletions
@ -1,7 +1,10 @@ |
|||||||
namespace StabilityMatrix.Avalonia.Models; |
using System.Collections.ObjectModel; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
public class PackageOutputCategory |
public class PackageOutputCategory |
||||||
{ |
{ |
||||||
|
public ObservableCollection<PackageOutputCategory> SubDirectories { get; set; } = new(); |
||||||
public required string Name { get; set; } |
public required string Name { get; set; } |
||||||
public required string Path { get; set; } |
public required string Path { get; set; } |
||||||
} |
} |
||||||
|
@ -0,0 +1,148 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Immutable; |
||||||
|
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.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<RunningPackageService> logger, |
||||||
|
IPackageFactory packageFactory, |
||||||
|
INotificationService notificationService, |
||||||
|
ISettingsManager settingsManager, |
||||||
|
IPyRunner pyRunner |
||||||
|
) : ObservableObject |
||||||
|
{ |
||||||
|
// 🤔 what if we put the ConsoleViewModel inside the BasePackage? 🤔 |
||||||
|
[ObservableProperty] |
||||||
|
private ObservableDictionary<Guid, RunningPackageViewModel> runningPackages = []; |
||||||
|
|
||||||
|
public async Task<PackagePair?> 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<LaunchOption>()) |
||||||
|
.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); |
||||||
|
|
||||||
|
var viewModel = new RunningPackageViewModel( |
||||||
|
settingsManager, |
||||||
|
notificationService, |
||||||
|
this, |
||||||
|
runningPackage, |
||||||
|
console |
||||||
|
); |
||||||
|
RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel); |
||||||
|
|
||||||
|
return runningPackage; |
||||||
|
} |
||||||
|
|
||||||
|
public async Task StopPackage(Guid id) |
||||||
|
{ |
||||||
|
if (RunningPackages.TryGetValue(id, out var vm)) |
||||||
|
{ |
||||||
|
var runningPackage = vm.RunningPackage; |
||||||
|
await runningPackage.BasePackage.WaitForShutdown(); |
||||||
|
RunningPackages.Remove(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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,403 @@ |
|||||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> |
||||||
|
<Design.PreviewWith> |
||||||
|
<Border Padding="20"> |
||||||
|
<ui:CommandBar |
||||||
|
Margin="8" |
||||||
|
VerticalAlignment="Center" |
||||||
|
VerticalContentAlignment="Center" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
HorizontalContentAlignment="Left" |
||||||
|
DefaultLabelPosition="Right"> |
||||||
|
<ui:CommandBar.PrimaryCommands> |
||||||
|
<ui:CommandBarButton Classes="success" Label="Success Button" Margin="8" |
||||||
|
HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Classes="accent" Label="FA Accent Button" Margin="8" |
||||||
|
HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Classes="systemaccent" Label="System Accent Button" Margin="8" |
||||||
|
HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Classes="danger" Label="Danger Button" Margin="8" HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Classes="info" Label="Info Button" Margin="8" HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Classes="transparent-info" Label="Semi-Transparent Info Button" Margin="8" |
||||||
|
HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Classes="transparent" Label="Transparent Button" Margin="8" |
||||||
|
HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Classes="transparent-full" Label="Transparent Button" Margin="8" |
||||||
|
HorizontalAlignment="Center" /> |
||||||
|
<ui:CommandBarButton Label="Disabled Button" Margin="8" IsEnabled="False" |
||||||
|
HorizontalAlignment="Center" /> |
||||||
|
</ui:CommandBar.PrimaryCommands> |
||||||
|
</ui:CommandBar> |
||||||
|
</Border> |
||||||
|
</Design.PreviewWith> |
||||||
|
|
||||||
|
<!-- Success --> |
||||||
|
<Style Selector="ui|CommandBarButton.success"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeGreenColor}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeGreenColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^ /template/ TextBlock#TextLabel"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkGreenColor}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkGreenColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="Green" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkDarkGreenColor}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkDarkGreenColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!-- Danger --> |
||||||
|
<Style Selector="ui|CommandBarButton.danger"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeRedColor}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeRedColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkRedColor}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkRedColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkDarkRedColor}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkDarkRedColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!-- Info --> |
||||||
|
<Style Selector="ui|CommandBarButton.info"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeLightBlueColor}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeLightBlueColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeBlueColor}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBlueColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkBlueColor}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkBlueColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!--Accent Button--> |
||||||
|
<Style Selector="ui|CommandBarButton.accent"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!-- SystemAccent --> |
||||||
|
<Style Selector="ui|CommandBarButton.systemaccent"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark1}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorDark1}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark2}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorDark2}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!-- Transparent --> |
||||||
|
<Style Selector="ui|CommandBarButton.transparent"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundPressed}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!-- Semi-Transparent Info --> |
||||||
|
<Style Selector="ui|CommandBarButton.transparent-info"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeLightBlueColorTransparent}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeLightBlueColorTransparent}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeBlueColorTransparent}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBlueColorTransparent}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkBlueColorTransparent}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkBlueColorTransparent}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!-- Transparent red --> |
||||||
|
<Style Selector="ui|CommandBarButton.transparent-red"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeCoralRedColor}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeCoralRedColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkCoralRedColor}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkCoralRedColor}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<!-- Full Transparent --> |
||||||
|
<Style Selector="ui|CommandBarButton.transparent-full"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlTransparentBrush}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}"/> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pressed"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBackgroundPressed}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:disabled"> |
||||||
|
<Style Selector="^ /template/ Border#AppBarButtonInnerBorder"> |
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemControlTransparentBrush}" /> |
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlTransparentBrush}" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Styles> |
@ -0,0 +1,161 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using CommunityToolkit.Mvvm.Input; |
||||||
|
using FluentAvalonia.UI.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Services; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||||
|
using StabilityMatrix.Avalonia.Views; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
using StabilityMatrix.Core.Helper; |
||||||
|
using StabilityMatrix.Core.Models; |
||||||
|
using StabilityMatrix.Core.Models.Packages; |
||||||
|
using StabilityMatrix.Core.Processes; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource; |
||||||
|
using TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.ViewModels; |
||||||
|
|
||||||
|
[View(typeof(ConsoleOutputPage))] |
||||||
|
public partial class RunningPackageViewModel : PageViewModelBase, IDisposable, IAsyncDisposable |
||||||
|
{ |
||||||
|
private readonly ISettingsManager settingsManager; |
||||||
|
private readonly INotificationService notificationService; |
||||||
|
private readonly RunningPackageService runningPackageService; |
||||||
|
|
||||||
|
public PackagePair RunningPackage { get; } |
||||||
|
public ConsoleViewModel Console { get; } |
||||||
|
public override string Title => RunningPackage.InstalledPackage.PackageName ?? "Running Package"; |
||||||
|
public override IconSource IconSource => new SymbolIconSource(); |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool autoScrollToEnd; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool showWebUiButton; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string webUiUrl = string.Empty; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool isRunning = true; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string consoleInput = string.Empty; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool showWebUiTeachingTip; |
||||||
|
|
||||||
|
/// <inheritdoc/> |
||||||
|
public RunningPackageViewModel( |
||||||
|
ISettingsManager settingsManager, |
||||||
|
INotificationService notificationService, |
||||||
|
RunningPackageService runningPackageService, |
||||||
|
PackagePair runningPackage, |
||||||
|
ConsoleViewModel console |
||||||
|
) |
||||||
|
{ |
||||||
|
this.settingsManager = settingsManager; |
||||||
|
this.notificationService = notificationService; |
||||||
|
this.runningPackageService = runningPackageService; |
||||||
|
|
||||||
|
RunningPackage = runningPackage; |
||||||
|
Console = console; |
||||||
|
Console.Document.LineCountChanged += DocumentOnLineCountChanged; |
||||||
|
RunningPackage.BasePackage.StartupComplete += BasePackageOnStartupComplete; |
||||||
|
RunningPackage.BasePackage.Exited += BasePackageOnExited; |
||||||
|
|
||||||
|
settingsManager.RelayPropertyFor( |
||||||
|
this, |
||||||
|
vm => vm.AutoScrollToEnd, |
||||||
|
settings => settings.AutoScrollLaunchConsoleToEnd, |
||||||
|
true |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private void BasePackageOnExited(object? sender, int e) |
||||||
|
{ |
||||||
|
IsRunning = false; |
||||||
|
ShowWebUiButton = false; |
||||||
|
Console.Document.LineCountChanged -= DocumentOnLineCountChanged; |
||||||
|
RunningPackage.BasePackage.StartupComplete -= BasePackageOnStartupComplete; |
||||||
|
RunningPackage.BasePackage.Exited -= BasePackageOnExited; |
||||||
|
runningPackageService.RunningPackages.Remove(RunningPackage.InstalledPackage.Id); |
||||||
|
} |
||||||
|
|
||||||
|
private void BasePackageOnStartupComplete(object? sender, string url) |
||||||
|
{ |
||||||
|
WebUiUrl = url.Replace("0.0.0.0", "127.0.0.1"); |
||||||
|
ShowWebUiButton = !string.IsNullOrWhiteSpace(WebUiUrl); |
||||||
|
|
||||||
|
if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.WebUiButtonMovedTip)) |
||||||
|
return; |
||||||
|
|
||||||
|
ShowWebUiTeachingTip = true; |
||||||
|
settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.WebUiButtonMovedTip)); |
||||||
|
} |
||||||
|
|
||||||
|
private void DocumentOnLineCountChanged(object? sender, EventArgs e) |
||||||
|
{ |
||||||
|
if (AutoScrollToEnd) |
||||||
|
{ |
||||||
|
EventManager.Instance.OnScrollToBottomRequested(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private void LaunchPackage() |
||||||
|
{ |
||||||
|
EventManager.Instance.OnPackageRelaunchRequested(RunningPackage.InstalledPackage); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task Stop() |
||||||
|
{ |
||||||
|
await runningPackageService.StopPackage(RunningPackage.InstalledPackage.Id); |
||||||
|
Console.PostLine($"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}"); |
||||||
|
await Console.StopUpdatesAsync(); |
||||||
|
IsRunning = false; |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private void LaunchWebUi() |
||||||
|
{ |
||||||
|
if (string.IsNullOrEmpty(WebUiUrl)) |
||||||
|
return; |
||||||
|
|
||||||
|
notificationService.TryAsync( |
||||||
|
Task.Run(() => ProcessRunner.OpenUrl(WebUiUrl)), |
||||||
|
"Failed to open URL", |
||||||
|
$"{WebUiUrl}" |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task SendToConsole() |
||||||
|
{ |
||||||
|
Console.PostLine(ConsoleInput); |
||||||
|
if (RunningPackage?.BasePackage is BaseGitPackage gitPackage) |
||||||
|
{ |
||||||
|
var venv = gitPackage.VenvRunner; |
||||||
|
var process = venv?.Process; |
||||||
|
if (process is not null) |
||||||
|
{ |
||||||
|
await process.StandardInput.WriteLineAsync(ConsoleInput); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ConsoleInput = string.Empty; |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
Console.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
public async ValueTask DisposeAsync() |
||||||
|
{ |
||||||
|
await Console.DisposeAsync(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
<controls:UserControlBase xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||||
|
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||||
|
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit" |
||||||
|
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||||
|
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" |
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||||
|
x:DataType="viewModels:RunningPackageViewModel" |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.ConsoleOutputPage"> |
||||||
|
<Grid RowDefinitions="Auto, *, Auto"> |
||||||
|
<ui:CommandBar Grid.Row="0" |
||||||
|
Margin="12,0,0,4" |
||||||
|
VerticalAlignment="Center" |
||||||
|
VerticalContentAlignment="Center" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
HorizontalContentAlignment="Left" |
||||||
|
DefaultLabelPosition="Right"> |
||||||
|
<ui:CommandBar.PrimaryCommands> |
||||||
|
<ui:CommandBarButton |
||||||
|
IconSource="Stop" |
||||||
|
IsVisible="{Binding IsRunning}" |
||||||
|
Classes="danger" |
||||||
|
Command="{Binding StopCommand}" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Label="{x:Static lang:Resources.Action_Stop}" /> |
||||||
|
<ui:CommandBarButton |
||||||
|
IconSource="Play" |
||||||
|
IsVisible="{Binding !IsRunning}" |
||||||
|
Classes="success" |
||||||
|
Command="{Binding LaunchPackageCommand}" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Label="{x:Static lang:Resources.Action_Launch}" /> |
||||||
|
|
||||||
|
<ui:CommandBarSeparator Margin="6,0,4,0"/> |
||||||
|
|
||||||
|
<ui:CommandBarToggleButton |
||||||
|
VerticalAlignment="Center" |
||||||
|
IsChecked="{Binding AutoScrollToEnd}" |
||||||
|
Label="{x:Static lang:Resources.Label_ToggleAutoScrolling}" |
||||||
|
ToolTip.Tip="{x:Static lang:Resources.Label_AutoScrollToEnd}"> |
||||||
|
<ui:CommandBarToggleButton.IconSource> |
||||||
|
<controls:FASymbolIconSource Symbol="fa-solid fa-arrow-down-wide-short" /> |
||||||
|
</ui:CommandBarToggleButton.IconSource> |
||||||
|
</ui:CommandBarToggleButton> |
||||||
|
|
||||||
|
<ui:CommandBarButton |
||||||
|
Command="{Binding LaunchWebUiCommand}" |
||||||
|
IsVisible="{Binding ShowWebUiButton}" |
||||||
|
VerticalAlignment="Center" |
||||||
|
x:Name="OpenWebUiButton" |
||||||
|
Label="{x:Static lang:Resources.Action_OpenWebUI}"> |
||||||
|
<ui:CommandBarButton.IconSource> |
||||||
|
<controls:FASymbolIconSource Symbol="fa-solid fa-up-right-from-square"/> |
||||||
|
</ui:CommandBarButton.IconSource> |
||||||
|
</ui:CommandBarButton> |
||||||
|
</ui:CommandBar.PrimaryCommands> |
||||||
|
</ui:CommandBar> |
||||||
|
|
||||||
|
<avaloniaEdit:TextEditor |
||||||
|
Grid.Row="1" |
||||||
|
x:Name="Console" |
||||||
|
Margin="8,8,16,10" |
||||||
|
DataContext="{Binding Console}" |
||||||
|
Document="{Binding Document}" |
||||||
|
FontFamily="Cascadia Code,Consolas,Menlo,Monospace" |
||||||
|
IsReadOnly="True" |
||||||
|
LineNumbersForeground="DarkSlateGray" |
||||||
|
ShowLineNumbers="True" |
||||||
|
VerticalScrollBarVisibility="Auto" |
||||||
|
WordWrap="True" /> |
||||||
|
|
||||||
|
<Grid Grid.Row="2" ColumnDefinitions="*, Auto" |
||||||
|
Margin="16,4,16,16"> |
||||||
|
<TextBox Grid.Column="0" Text="{Binding ConsoleInput, Mode=TwoWay}" |
||||||
|
Margin="0,0,8,0"/> |
||||||
|
<Button Grid.Column="1" |
||||||
|
Classes="accent" |
||||||
|
IsDefault="True" |
||||||
|
Content="{x:Static lang:Resources.Action_Send}" |
||||||
|
Command="{Binding SendToConsoleCommand}"/> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<ui:TeachingTip Grid.Row="0" Grid.Column="0" Name="TeachingTip1" |
||||||
|
Target="{Binding #OpenWebUiButton}" |
||||||
|
Title="{x:Static lang:Resources.TeachingTip_WebUiButtonMoved}" |
||||||
|
PreferredPlacement="Bottom" |
||||||
|
IsOpen="{Binding ShowWebUiTeachingTip}"/> |
||||||
|
</Grid> |
||||||
|
</controls:UserControlBase> |
@ -0,0 +1,54 @@ |
|||||||
|
using System; |
||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Controls.Primitives; |
||||||
|
using Avalonia.Interactivity; |
||||||
|
using Avalonia.Threading; |
||||||
|
using AvaloniaEdit; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Helpers; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
using StabilityMatrix.Core.Helper; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Views; |
||||||
|
|
||||||
|
[Transient] |
||||||
|
public partial class ConsoleOutputPage : UserControlBase |
||||||
|
{ |
||||||
|
private const int LineOffset = 5; |
||||||
|
|
||||||
|
public ConsoleOutputPage() |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||||
|
{ |
||||||
|
base.OnApplyTemplate(e); |
||||||
|
TextEditorConfigs.Configure(Console, TextEditorPreset.Console); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void OnUnloaded(RoutedEventArgs e) |
||||||
|
{ |
||||||
|
base.OnUnloaded(e); |
||||||
|
EventManager.Instance.ScrollToBottomRequested -= OnScrollToBottomRequested; |
||||||
|
} |
||||||
|
|
||||||
|
protected override void OnLoaded(RoutedEventArgs e) |
||||||
|
{ |
||||||
|
base.OnLoaded(e); |
||||||
|
EventManager.Instance.ScrollToBottomRequested += OnScrollToBottomRequested; |
||||||
|
} |
||||||
|
|
||||||
|
private void OnScrollToBottomRequested(object? sender, EventArgs e) |
||||||
|
{ |
||||||
|
Dispatcher.UIThread.Invoke(() => |
||||||
|
{ |
||||||
|
var editor = this.FindControl<TextEditor>("Console"); |
||||||
|
if (editor?.Document == null) |
||||||
|
return; |
||||||
|
var line = Math.Max(editor.Document.LineCount - LineOffset, 1); |
||||||
|
editor.ScrollToLine(line); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,124 @@ |
|||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
|
using JetBrains.Annotations; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.Comfy; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Collection of preprocessors included in |
||||||
|
/// </summary> |
||||||
|
/// <param name="Value"></param> |
||||||
|
[PublicAPI] |
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")] |
||||||
|
public record ComfyAuxPreprocessor(string Value) : StringValue(Value) |
||||||
|
{ |
||||||
|
public static ComfyAuxPreprocessor None { get; } = new("none"); |
||||||
|
public static ComfyAuxPreprocessor AnimeFaceSemSegPreprocessor { get; } = |
||||||
|
new("AnimeFace_SemSegPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor BinaryPreprocessor { get; } = new("BinaryPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor CannyEdgePreprocessor { get; } = new("CannyEdgePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor ColorPreprocessor { get; } = new("ColorPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor DensePosePreprocessor { get; } = new("DensePosePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor DepthAnythingPreprocessor { get; } = new("DepthAnythingPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor ZoeDepthAnythingPreprocessor { get; } = |
||||||
|
new("Zoe_DepthAnythingPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor DiffusionEdgePreprocessor { get; } = new("DiffusionEdge_Preprocessor"); |
||||||
|
public static ComfyAuxPreprocessor DWPreprocessor { get; } = new("DWPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor AnimalPosePreprocessor { get; } = new("AnimalPosePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor HEDPreprocessor { get; } = new("HEDPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor FakeScribblePreprocessor { get; } = new("FakeScribblePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor LeReSDepthMapPreprocessor { get; } = new("LeReS-DepthMapPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor LineArtPreprocessor { get; } = new("LineArtPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor AnimeLineArtPreprocessor { get; } = new("AnimeLineArtPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor LineartStandardPreprocessor { get; } = |
||||||
|
new("LineartStandardPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor Manga2AnimeLineArtPreprocessor { get; } = |
||||||
|
new("Manga2Anime_LineArt_Preprocessor"); |
||||||
|
public static ComfyAuxPreprocessor MediaPipeFaceMeshPreprocessor { get; } = |
||||||
|
new("MediaPipe-FaceMeshPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor MeshGraphormerDepthMapPreprocessor { get; } = |
||||||
|
new("MeshGraphormer-DepthMapPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor MiDaSNormalMapPreprocessor { get; } = |
||||||
|
new("MiDaS-NormalMapPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor MiDaSDepthMapPreprocessor { get; } = new("MiDaS-DepthMapPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor MLSDPreprocessor { get; } = new("M-LSDPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor BAENormalMapPreprocessor { get; } = new("BAE-NormalMapPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor OneFormerCOCOSemSegPreprocessor { get; } = |
||||||
|
new("OneFormer-COCO-SemSegPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor OneFormerADE20KSemSegPreprocessor { get; } = |
||||||
|
new("OneFormer-ADE20K-SemSegPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor OpenposePreprocessor { get; } = new("OpenposePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor PiDiNetPreprocessor { get; } = new("PiDiNetPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor SavePoseKpsAsJsonFile { get; } = new("SavePoseKpsAsJsonFile"); |
||||||
|
public static ComfyAuxPreprocessor FacialPartColoringFromPoseKps { get; } = |
||||||
|
new("FacialPartColoringFromPoseKps"); |
||||||
|
public static ComfyAuxPreprocessor ImageLuminanceDetector { get; } = new("ImageLuminanceDetector"); |
||||||
|
public static ComfyAuxPreprocessor ImageIntensityDetector { get; } = new("ImageIntensityDetector"); |
||||||
|
public static ComfyAuxPreprocessor ScribblePreprocessor { get; } = new("ScribblePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor ScribbleXDoGPreprocessor { get; } = new("Scribble_XDoG_Preprocessor"); |
||||||
|
public static ComfyAuxPreprocessor SAMPreprocessor { get; } = new("SAMPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor ShufflePreprocessor { get; } = new("ShufflePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor TEEDPreprocessor { get; } = new("TEEDPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor TilePreprocessor { get; } = new("TilePreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor UniFormerSemSegPreprocessor { get; } = |
||||||
|
new("UniFormer-SemSegPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor SemSegPreprocessor { get; } = new("SemSegPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor UnimatchOptFlowPreprocessor { get; } = |
||||||
|
new("Unimatch_OptFlowPreprocessor"); |
||||||
|
public static ComfyAuxPreprocessor MaskOptFlow { get; } = new("MaskOptFlow"); |
||||||
|
public static ComfyAuxPreprocessor ZoeDepthMapPreprocessor { get; } = new("Zoe-DepthMapPreprocessor"); |
||||||
|
|
||||||
|
private static Dictionary<ComfyAuxPreprocessor, string> DisplayNamesMapping { get; } = |
||||||
|
new() |
||||||
|
{ |
||||||
|
[None] = "None", |
||||||
|
[AnimeFaceSemSegPreprocessor] = "Anime Face SemSeg Preprocessor", |
||||||
|
[BinaryPreprocessor] = "Binary Preprocessor", |
||||||
|
[CannyEdgePreprocessor] = "Canny Edge Preprocessor", |
||||||
|
[ColorPreprocessor] = "Color Preprocessor", |
||||||
|
[DensePosePreprocessor] = "DensePose Preprocessor", |
||||||
|
[DepthAnythingPreprocessor] = "Depth Anything Preprocessor", |
||||||
|
[ZoeDepthAnythingPreprocessor] = "Zoe Depth Anything Preprocessor", |
||||||
|
[DiffusionEdgePreprocessor] = "Diffusion Edge Preprocessor", |
||||||
|
[DWPreprocessor] = "DW Preprocessor", |
||||||
|
[AnimalPosePreprocessor] = "Animal Pose Preprocessor", |
||||||
|
[HEDPreprocessor] = "HED Preprocessor", |
||||||
|
[FakeScribblePreprocessor] = "Fake Scribble Preprocessor", |
||||||
|
[LeReSDepthMapPreprocessor] = "LeReS-DepthMap Preprocessor", |
||||||
|
[LineArtPreprocessor] = "LineArt Preprocessor", |
||||||
|
[AnimeLineArtPreprocessor] = "Anime LineArt Preprocessor", |
||||||
|
[LineartStandardPreprocessor] = "Lineart Standard Preprocessor", |
||||||
|
[Manga2AnimeLineArtPreprocessor] = "Manga2Anime LineArt Preprocessor", |
||||||
|
[MediaPipeFaceMeshPreprocessor] = "MediaPipe FaceMesh Preprocessor", |
||||||
|
[MeshGraphormerDepthMapPreprocessor] = "MeshGraphormer DepthMap Preprocessor", |
||||||
|
[MiDaSNormalMapPreprocessor] = "MiDaS NormalMap Preprocessor", |
||||||
|
[MiDaSDepthMapPreprocessor] = "MiDaS DepthMap Preprocessor", |
||||||
|
[MLSDPreprocessor] = "M-LSD Preprocessor", |
||||||
|
[BAENormalMapPreprocessor] = "BAE NormalMap Preprocessor", |
||||||
|
[OneFormerCOCOSemSegPreprocessor] = "OneFormer COCO SemSeg Preprocessor", |
||||||
|
[OneFormerADE20KSemSegPreprocessor] = "OneFormer ADE20K SemSeg Preprocessor", |
||||||
|
[OpenposePreprocessor] = "Openpose Preprocessor", |
||||||
|
[PiDiNetPreprocessor] = "PiDiNet Preprocessor", |
||||||
|
[SavePoseKpsAsJsonFile] = "Save Pose Kps As Json File", |
||||||
|
[FacialPartColoringFromPoseKps] = "Facial Part Coloring From Pose Kps", |
||||||
|
[ImageLuminanceDetector] = "Image Luminance Detector", |
||||||
|
[ImageIntensityDetector] = "Image Intensity Detector", |
||||||
|
[ScribblePreprocessor] = "Scribble Preprocessor", |
||||||
|
[ScribbleXDoGPreprocessor] = "Scribble XDoG Preprocessor", |
||||||
|
[SAMPreprocessor] = "SAM Preprocessor", |
||||||
|
[ShufflePreprocessor] = "Shuffle Preprocessor", |
||||||
|
[TEEDPreprocessor] = "TEED Preprocessor", |
||||||
|
[TilePreprocessor] = "Tile Preprocessor", |
||||||
|
[UniFormerSemSegPreprocessor] = "UniFormer SemSeg Preprocessor", |
||||||
|
[SemSegPreprocessor] = "SemSeg Preprocessor", |
||||||
|
[UnimatchOptFlowPreprocessor] = "Unimatch OptFlow Preprocessor", |
||||||
|
[MaskOptFlow] = "Mask OptFlow", |
||||||
|
[ZoeDepthMapPreprocessor] = "Zoe DepthMap Preprocessor" |
||||||
|
}; |
||||||
|
|
||||||
|
public static IEnumerable<ComfyAuxPreprocessor> Defaults => DisplayNamesMapping.Keys; |
||||||
|
|
||||||
|
public string DisplayName => DisplayNamesMapping.GetValueOrDefault(this, Value); |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override string ToString() => Value; |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Inference; |
||||||
|
|
||||||
|
public class ModuleApplyStepTemporaryArgs |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Temporary Primary apply step, used by ControlNet ReferenceOnly which changes the latent. |
||||||
|
/// </summary> |
||||||
|
public PrimaryNodeConnection? Primary { get; set; } |
||||||
|
|
||||||
|
public VAENodeConnection? PrimaryVAE { get; set; } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Used by Reference-Only ControlNet to indicate that <see cref="Primary"/> has been batched. |
||||||
|
/// </summary> |
||||||
|
public bool IsPrimaryTempBatched { get; set; } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// When <see cref="IsPrimaryTempBatched"/> is true, this is the index of the temp batch to pick after sampling. |
||||||
|
/// </summary> |
||||||
|
public int PrimaryTempBatchPickIndex { get; set; } |
||||||
|
|
||||||
|
public Dictionary<string, ModelConnections> Models { get; set; } = |
||||||
|
new() { ["Base"] = new ModelConnections("Base"), ["Refiner"] = new ModelConnections("Refiner") }; |
||||||
|
|
||||||
|
public ModelConnections Base => Models["Base"]; |
||||||
|
public ModelConnections Refiner => Models["Refiner"]; |
||||||
|
|
||||||
|
public ConditioningConnections GetRefinerOrBaseConditioning() |
||||||
|
{ |
||||||
|
return Refiner.Conditioning |
||||||
|
?? Base.Conditioning |
||||||
|
?? throw new NullReferenceException("No Refiner or Base Conditioning"); |
||||||
|
} |
||||||
|
|
||||||
|
public ModelNodeConnection GetRefinerOrBaseModel() |
||||||
|
{ |
||||||
|
return Refiner.Model ?? Base.Model ?? throw new NullReferenceException("No Refiner or Base Model"); |
||||||
|
} |
||||||
|
|
||||||
|
public VAENodeConnection GetDefaultVAE() |
||||||
|
{ |
||||||
|
return PrimaryVAE ?? Refiner.VAE ?? Base.VAE ?? throw new NullReferenceException("No VAE"); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
using StabilityMatrix.Core.Models.Api.Comfy.Nodes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models; |
||||||
|
|
||||||
|
public class InferenceQueueCustomPromptEventArgs : EventArgs |
||||||
|
{ |
||||||
|
public ComfyNodeBuilder Builder { get; } = new(); |
||||||
|
|
||||||
|
public NodeDictionary Nodes => Builder.Nodes; |
||||||
|
|
||||||
|
public long? SeedOverride { get; init; } |
||||||
|
|
||||||
|
public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = []; |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
|
using System.Text.RegularExpressions; |
||||||
|
using JetBrains.Annotations; |
||||||
|
using Semver; |
||||||
|
using StabilityMatrix.Core.Processes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Packages.Extensions; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Extension specifier with optional version constraints. |
||||||
|
/// </summary> |
||||||
|
[PublicAPI] |
||||||
|
public partial class ExtensionSpecifier |
||||||
|
{ |
||||||
|
public required string Name { get; init; } |
||||||
|
|
||||||
|
public string? Constraint { get; init; } |
||||||
|
|
||||||
|
public string? Version { get; init; } |
||||||
|
|
||||||
|
public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Version; |
||||||
|
|
||||||
|
public bool TryGetSemVersionRange([NotNullWhen(true)] out SemVersionRange? semVersionRange) |
||||||
|
{ |
||||||
|
if (!string.IsNullOrEmpty(VersionConstraint)) |
||||||
|
{ |
||||||
|
return SemVersionRange.TryParse( |
||||||
|
VersionConstraint, |
||||||
|
SemVersionRangeOptions.Loose, |
||||||
|
out semVersionRange |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
semVersionRange = null; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public static ExtensionSpecifier Parse(string value) |
||||||
|
{ |
||||||
|
TryParse(value, true, out var packageSpecifier); |
||||||
|
|
||||||
|
return packageSpecifier!; |
||||||
|
} |
||||||
|
|
||||||
|
public static bool TryParse(string value, [NotNullWhen(true)] out ExtensionSpecifier? extensionSpecifier) |
||||||
|
{ |
||||||
|
return TryParse(value, false, out extensionSpecifier); |
||||||
|
} |
||||||
|
|
||||||
|
private static bool TryParse( |
||||||
|
string value, |
||||||
|
bool throwOnFailure, |
||||||
|
[NotNullWhen(true)] out ExtensionSpecifier? packageSpecifier |
||||||
|
) |
||||||
|
{ |
||||||
|
var match = ExtensionSpecifierRegex().Match(value); |
||||||
|
if (!match.Success) |
||||||
|
{ |
||||||
|
if (throwOnFailure) |
||||||
|
{ |
||||||
|
throw new ArgumentException($"Invalid extension specifier: {value}"); |
||||||
|
} |
||||||
|
|
||||||
|
packageSpecifier = null; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
packageSpecifier = new ExtensionSpecifier |
||||||
|
{ |
||||||
|
Name = match.Groups["extension_name"].Value, |
||||||
|
Constraint = match.Groups["version_constraint"].Value, |
||||||
|
Version = match.Groups["version"].Value |
||||||
|
}; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override string ToString() |
||||||
|
{ |
||||||
|
return Name + VersionConstraint; |
||||||
|
} |
||||||
|
|
||||||
|
public static implicit operator Argument(ExtensionSpecifier specifier) |
||||||
|
{ |
||||||
|
return specifier.VersionConstraint is null |
||||||
|
? new Argument(specifier.Name) |
||||||
|
: new Argument((specifier.Name, specifier.VersionConstraint)); |
||||||
|
} |
||||||
|
|
||||||
|
public static implicit operator ExtensionSpecifier(string specifier) |
||||||
|
{ |
||||||
|
return Parse(specifier); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Regex to match a pip package specifier. |
||||||
|
/// </summary> |
||||||
|
[GeneratedRegex( |
||||||
|
@"(?<extension_name>\S+)\s*(?<version_constraint>==|>=|<=|>|<|~=|!=)?\s*(?<version>[a-zA-Z0-9_.]+)?", |
||||||
|
RegexOptions.CultureInvariant, |
||||||
|
5000 |
||||||
|
)] |
||||||
|
private static partial Regex ExtensionSpecifierRegex(); |
||||||
|
} |
Loading…
Reference in new issue