JT
8 months ago
committed by
GitHub
112 changed files with 4688 additions and 1161 deletions
@ -1,7 +1,10 @@
|
||||
namespace StabilityMatrix.Avalonia.Models; |
||||
using System.Collections.ObjectModel; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Models; |
||||
|
||||
public class PackageOutputCategory |
||||
{ |
||||
public ObservableCollection<PackageOutputCategory> SubDirectories { get; set; } = new(); |
||||
public required string Name { get; set; } |
||||
public required string Path { get; set; } |
||||
} |
||||
|
@ -0,0 +1,8 @@
|
||||
using System.Threading.Tasks; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Services; |
||||
|
||||
public interface IModelDownloadLinkHandler |
||||
{ |
||||
Task StartListening(); |
||||
} |
@ -0,0 +1,248 @@
|
||||
using System; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using System.Web; |
||||
using Avalonia.Controls.Notifications; |
||||
using Avalonia.Threading; |
||||
using MessagePipe; |
||||
using Microsoft.Extensions.Logging; |
||||
using StabilityMatrix.Avalonia.Helpers; |
||||
using StabilityMatrix.Core.Api; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Extensions; |
||||
using StabilityMatrix.Core.Models; |
||||
using StabilityMatrix.Core.Models.Api; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
using StabilityMatrix.Core.Services; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Services; |
||||
|
||||
[Singleton(typeof(IModelDownloadLinkHandler)), Singleton(typeof(IAsyncDisposable))] |
||||
public class ModelDownloadLinkHandler( |
||||
IDistributedSubscriber<string, Uri> uriHandlerSubscriber, |
||||
ILogger<ModelDownloadLinkHandler> logger, |
||||
ICivitApi civitApi, |
||||
INotificationService notificationService, |
||||
ISettingsManager settingsManager, |
||||
IDownloadService downloadService, |
||||
ITrackedDownloadService trackedDownloadService |
||||
) : IAsyncDisposable, IModelDownloadLinkHandler |
||||
{ |
||||
private IAsyncDisposable? uriHandlerSubscription; |
||||
private const string DownloadCivitModel = "downloadCivitModel"; |
||||
|
||||
public async Task StartListening() |
||||
{ |
||||
uriHandlerSubscription = await uriHandlerSubscriber.SubscribeAsync( |
||||
UriHandler.IpcKeySend, |
||||
UriReceivedHandler |
||||
); |
||||
} |
||||
|
||||
public async ValueTask DisposeAsync() |
||||
{ |
||||
if (uriHandlerSubscription is not null) |
||||
{ |
||||
await uriHandlerSubscription.DisposeAsync(); |
||||
uriHandlerSubscription = null; |
||||
} |
||||
} |
||||
|
||||
private void UriReceivedHandler(Uri receivedUri) |
||||
{ |
||||
logger.LogDebug("ModelDownloadLinkHandler Received URI: {Uri}", receivedUri.PathAndQuery); |
||||
if (!receivedUri.Host.Equals(DownloadCivitModel, StringComparison.OrdinalIgnoreCase)) |
||||
return; |
||||
|
||||
var queryDict = HttpUtility.ParseQueryString(receivedUri.Query); |
||||
var modelIdStr = queryDict["modelId"]; |
||||
var modelVersionIdStr = queryDict["modelVersionId"]; |
||||
var type = queryDict["type"]; |
||||
var format = queryDict["format"]; |
||||
var size = queryDict["size"]; |
||||
var fp = queryDict["fp"]; |
||||
|
||||
if ( |
||||
string.IsNullOrWhiteSpace(modelIdStr) |
||||
|| string.IsNullOrWhiteSpace(type) |
||||
|| string.IsNullOrWhiteSpace(format) |
||||
|| !int.TryParse(modelIdStr, out var modelId) |
||||
|| !Enum.TryParse<CivitFileType>(type, out var civitFileType) |
||||
|| !Enum.TryParse<CivitModelFormat>(format, out var civitFormat) |
||||
) |
||||
{ |
||||
logger.LogError("ModelDownloadLinkHandler: Invalid query parameters"); |
||||
|
||||
Dispatcher.UIThread.Post( |
||||
() => |
||||
notificationService.Show( |
||||
new Notification( |
||||
"Invalid Download Link", |
||||
"The download link is invalid", |
||||
NotificationType.Error |
||||
) |
||||
) |
||||
); |
||||
return; |
||||
} |
||||
|
||||
Dispatcher.UIThread.Post( |
||||
() => |
||||
notificationService.Show( |
||||
"Link Received", |
||||
"Successfully received download link", |
||||
NotificationType.Warning |
||||
) |
||||
); |
||||
|
||||
var modelTask = civitApi.GetModelById(modelId); |
||||
modelTask.Wait(); |
||||
var model = modelTask.Result; |
||||
|
||||
var useModelVersion = !string.IsNullOrWhiteSpace(modelVersionIdStr); |
||||
var modelVersionId = useModelVersion ? int.Parse(modelVersionIdStr) : 0; |
||||
|
||||
var modelVersion = useModelVersion |
||||
? model.ModelVersions?.FirstOrDefault(x => x.Id == modelVersionId) |
||||
: model.ModelVersions?.FirstOrDefault(); |
||||
|
||||
if (modelVersion is null) |
||||
{ |
||||
logger.LogError("ModelDownloadLinkHandler: Model version not found"); |
||||
Dispatcher.UIThread.Post( |
||||
() => |
||||
notificationService.Show( |
||||
new Notification( |
||||
"Model has no versions available", |
||||
"This model has no versions available for download", |
||||
NotificationType.Error |
||||
) |
||||
) |
||||
); |
||||
return; |
||||
} |
||||
|
||||
var possibleFiles = modelVersion.Files?.Where( |
||||
x => x.Type == civitFileType && x.Metadata.Format == civitFormat |
||||
); |
||||
|
||||
if (!string.IsNullOrWhiteSpace(fp) && Enum.TryParse<CivitModelFpType>(fp, out var fpType)) |
||||
{ |
||||
possibleFiles = possibleFiles?.Where(x => x.Metadata.Fp == fpType); |
||||
} |
||||
|
||||
if (!string.IsNullOrWhiteSpace(size) && Enum.TryParse<CivitModelSize>(size, out var modelSize)) |
||||
{ |
||||
possibleFiles = possibleFiles?.Where(x => x.Metadata.Size == modelSize); |
||||
} |
||||
|
||||
possibleFiles = possibleFiles?.ToList(); |
||||
|
||||
if (possibleFiles is null) |
||||
{ |
||||
Dispatcher.UIThread.Post( |
||||
() => |
||||
notificationService.Show( |
||||
new Notification( |
||||
"Model has no files available", |
||||
"This model has no files available for download", |
||||
NotificationType.Error |
||||
) |
||||
) |
||||
); |
||||
logger.LogError("ModelDownloadLinkHandler: Model file not found"); |
||||
return; |
||||
} |
||||
|
||||
var selectedFile = possibleFiles.FirstOrDefault() ?? modelVersion.Files?.FirstOrDefault(); |
||||
|
||||
var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory); |
||||
var downloadDirectory = rootModelsDirectory.JoinDir( |
||||
selectedFile.Type == CivitFileType.VAE |
||||
? SharedFolderType.VAE.GetStringValue() |
||||
: model.Type.ConvertTo<SharedFolderType>().GetStringValue() |
||||
); |
||||
|
||||
downloadDirectory.Create(); |
||||
var downloadPath = downloadDirectory.JoinFile(selectedFile.Name); |
||||
|
||||
// Create tracked download |
||||
var download = trackedDownloadService.NewDownload(selectedFile.DownloadUrl, downloadPath); |
||||
|
||||
// Download model info and preview first |
||||
var saveCmInfoTask = SaveCmInfo(model, modelVersion, selectedFile, downloadDirectory); |
||||
var savePreviewImageTask = SavePreviewImage(modelVersion, downloadPath); |
||||
|
||||
Task.WaitAll([saveCmInfoTask, savePreviewImageTask]); |
||||
|
||||
var cmInfoPath = saveCmInfoTask.Result; |
||||
var previewImagePath = savePreviewImageTask.Result; |
||||
|
||||
// Add hash info |
||||
download.ExpectedHashSha256 = selectedFile.Hashes.SHA256; |
||||
|
||||
// Add files to cleanup list |
||||
download.ExtraCleanupFileNames.Add(cmInfoPath); |
||||
if (previewImagePath is not null) |
||||
{ |
||||
download.ExtraCleanupFileNames.Add(previewImagePath); |
||||
} |
||||
|
||||
// Add hash context action |
||||
download.ContextAction = CivitPostDownloadContextAction.FromCivitFile(selectedFile); |
||||
|
||||
download.Start(); |
||||
|
||||
Dispatcher.UIThread.Post( |
||||
() => notificationService.Show("Download Started", $"Downloading {selectedFile.Name}") |
||||
); |
||||
} |
||||
|
||||
private static async Task<FilePath> SaveCmInfo( |
||||
CivitModel model, |
||||
CivitModelVersion modelVersion, |
||||
CivitFile modelFile, |
||||
DirectoryPath downloadDirectory |
||||
) |
||||
{ |
||||
var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Name); |
||||
var modelInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTime.UtcNow); |
||||
|
||||
await modelInfo.SaveJsonToDirectory(downloadDirectory, modelFileName); |
||||
|
||||
var jsonName = $"{modelFileName}.cm-info.json"; |
||||
return downloadDirectory.JoinFile(jsonName); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Saves the preview image to the same directory as the model file |
||||
/// </summary> |
||||
/// <param name="modelVersion"></param> |
||||
/// <param name="modelFilePath"></param> |
||||
/// <returns>The file path of the saved preview image</returns> |
||||
private async Task<FilePath?> SavePreviewImage(CivitModelVersion modelVersion, FilePath modelFilePath) |
||||
{ |
||||
// Skip if model has no images |
||||
if (modelVersion.Images == null || modelVersion.Images.Count == 0) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
var image = modelVersion.Images[0]; |
||||
var imageExtension = Path.GetExtension(image.Url).TrimStart('.'); |
||||
if (imageExtension is "jpg" or "jpeg" or "png") |
||||
{ |
||||
var imageDownloadPath = modelFilePath.Directory!.JoinFile( |
||||
$"{modelFilePath.NameWithoutExtension}.preview.{imageExtension}" |
||||
); |
||||
|
||||
var imageTask = downloadService.DownloadToFileAsync(image.Url, imageDownloadPath); |
||||
await notificationService.TryAsync(imageTask, "Could not download preview image"); |
||||
|
||||
return imageDownloadPath; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
@ -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,21 @@
|
||||
using System; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
using StabilityMatrix.Avalonia.Views.Dialogs; |
||||
using StabilityMatrix.Core.Attributes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||
|
||||
[View(typeof(ConfirmPackageDeleteDialog))] |
||||
[ManagedService] |
||||
[Transient] |
||||
public partial class ConfirmPackageDeleteDialogViewModel : ContentDialogViewModelBase |
||||
{ |
||||
public required string ExpectedPackageName { get; set; } |
||||
|
||||
[ObservableProperty] |
||||
[NotifyPropertyChangedFor(nameof(IsValid))] |
||||
private string packageName = string.Empty; |
||||
|
||||
public bool IsValid => ExpectedPackageName.Equals(PackageName, StringComparison.Ordinal); |
||||
} |
@ -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,55 @@
|
||||
<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:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs" |
||||
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
x:DataType="dialogs:ConfirmPackageDeleteDialogViewModel" |
||||
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.ConfirmPackageDeleteDialog"> |
||||
<Grid RowDefinitions="Auto, Auto, Auto, Auto, *" |
||||
Margin="8"> |
||||
<TextBlock Text="{x:Static lang:Resources.Text_PackageUninstall_Details}" |
||||
FontWeight="Bold" |
||||
FontSize="20" |
||||
TextAlignment="Center" |
||||
TextWrapping="Wrap"/> |
||||
<TextBlock Grid.Row="1" |
||||
Margin="0,32,0,8" |
||||
TextAlignment="Center"> |
||||
<Run Text="Please type"/> |
||||
<Run FontWeight="Bold" Text="{Binding ExpectedPackageName}"/> |
||||
<Run Text="to confirm the deletion of the package:"/> |
||||
</TextBlock> |
||||
|
||||
<TextBox Grid.Row="2" |
||||
Text="{Binding PackageName, Mode=TwoWay}" |
||||
Margin="0,16,0,0"/> |
||||
|
||||
<controls1:InfoBar Grid.Row="3" |
||||
Margin="0,16,0,0" |
||||
IsClosable="False" |
||||
IsOpen="True" |
||||
Title="{x:Static lang:Resources.Label_ActionCannotBeUndone}" |
||||
Severity="Warning"/> |
||||
|
||||
<UniformGrid Grid.Row="4" HorizontalAlignment="Stretch" |
||||
VerticalAlignment="Bottom" |
||||
Margin="0,32,0,0"> |
||||
<Button Content="{x:Static lang:Resources.Action_Delete}" |
||||
Classes="danger" |
||||
IsEnabled="{Binding IsValid}" |
||||
HorizontalAlignment="Stretch" |
||||
Margin="0,0,4,0" |
||||
Command="{Binding OnPrimaryButtonClick}" |
||||
FontSize="16"/> |
||||
<Button Content="{x:Static lang:Resources.Action_Cancel}" |
||||
Command="{Binding OnCloseButtonClick}" |
||||
Margin="4,0,0,0" |
||||
HorizontalAlignment="Stretch" |
||||
FontSize="16"/> |
||||
</UniformGrid> |
||||
</Grid> |
||||
</controls:UserControlBase> |
@ -0,0 +1,13 @@
|
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Core.Attributes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views.Dialogs; |
||||
|
||||
[Transient] |
||||
public partial class ConfirmPackageDeleteDialog : UserControlBase |
||||
{ |
||||
public ConfirmPackageDeleteDialog() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
} |
@ -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; } = []; |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue