Ionite
8 months ago
90 changed files with 11439 additions and 450 deletions
@ -0,0 +1,208 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Avalonia; |
||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Input; |
||||||
|
using Avalonia.Interactivity; |
||||||
|
using Avalonia.Xaml.Interactions.DragAndDrop; |
||||||
|
using Avalonia.Xaml.Interactivity; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Controls; |
||||||
|
|
||||||
|
public class BetterContextDragBehavior : Behavior<Control> |
||||||
|
{ |
||||||
|
private Point _dragStartPoint; |
||||||
|
private PointerEventArgs? _triggerEvent; |
||||||
|
private bool _lock; |
||||||
|
private bool _captured; |
||||||
|
|
||||||
|
public static readonly StyledProperty<object?> ContextProperty = AvaloniaProperty.Register< |
||||||
|
ContextDragBehavior, |
||||||
|
object? |
||||||
|
>(nameof(Context)); |
||||||
|
|
||||||
|
public static readonly StyledProperty<IDragHandler?> HandlerProperty = AvaloniaProperty.Register< |
||||||
|
ContextDragBehavior, |
||||||
|
IDragHandler? |
||||||
|
>(nameof(Handler)); |
||||||
|
|
||||||
|
public static readonly StyledProperty<double> HorizontalDragThresholdProperty = AvaloniaProperty.Register< |
||||||
|
ContextDragBehavior, |
||||||
|
double |
||||||
|
>(nameof(HorizontalDragThreshold), 3); |
||||||
|
|
||||||
|
public static readonly StyledProperty<double> VerticalDragThresholdProperty = AvaloniaProperty.Register< |
||||||
|
ContextDragBehavior, |
||||||
|
double |
||||||
|
>(nameof(VerticalDragThreshold), 3); |
||||||
|
|
||||||
|
public static readonly StyledProperty<string> DataFormatProperty = AvaloniaProperty.Register< |
||||||
|
BetterContextDragBehavior, |
||||||
|
string |
||||||
|
>("DataFormat"); |
||||||
|
|
||||||
|
public string DataFormat |
||||||
|
{ |
||||||
|
get => GetValue(DataFormatProperty); |
||||||
|
set => SetValue(DataFormatProperty, value); |
||||||
|
} |
||||||
|
|
||||||
|
public object? Context |
||||||
|
{ |
||||||
|
get => GetValue(ContextProperty); |
||||||
|
set => SetValue(ContextProperty, value); |
||||||
|
} |
||||||
|
|
||||||
|
public IDragHandler? Handler |
||||||
|
{ |
||||||
|
get => GetValue(HandlerProperty); |
||||||
|
set => SetValue(HandlerProperty, value); |
||||||
|
} |
||||||
|
|
||||||
|
public double HorizontalDragThreshold |
||||||
|
{ |
||||||
|
get => GetValue(HorizontalDragThresholdProperty); |
||||||
|
set => SetValue(HorizontalDragThresholdProperty, value); |
||||||
|
} |
||||||
|
|
||||||
|
public double VerticalDragThreshold |
||||||
|
{ |
||||||
|
get => GetValue(VerticalDragThresholdProperty); |
||||||
|
set => SetValue(VerticalDragThresholdProperty, value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
protected override void OnAttachedToVisualTree() |
||||||
|
{ |
||||||
|
AssociatedObject?.AddHandler( |
||||||
|
InputElement.PointerPressedEvent, |
||||||
|
AssociatedObject_PointerPressed, |
||||||
|
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||||
|
); |
||||||
|
AssociatedObject?.AddHandler( |
||||||
|
InputElement.PointerReleasedEvent, |
||||||
|
AssociatedObject_PointerReleased, |
||||||
|
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||||
|
); |
||||||
|
AssociatedObject?.AddHandler( |
||||||
|
InputElement.PointerMovedEvent, |
||||||
|
AssociatedObject_PointerMoved, |
||||||
|
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||||
|
); |
||||||
|
AssociatedObject?.AddHandler( |
||||||
|
InputElement.PointerCaptureLostEvent, |
||||||
|
AssociatedObject_CaptureLost, |
||||||
|
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
protected override void OnDetachedFromVisualTree() |
||||||
|
{ |
||||||
|
AssociatedObject?.RemoveHandler(InputElement.PointerPressedEvent, AssociatedObject_PointerPressed); |
||||||
|
AssociatedObject?.RemoveHandler(InputElement.PointerReleasedEvent, AssociatedObject_PointerReleased); |
||||||
|
AssociatedObject?.RemoveHandler(InputElement.PointerMovedEvent, AssociatedObject_PointerMoved); |
||||||
|
AssociatedObject?.RemoveHandler(InputElement.PointerCaptureLostEvent, AssociatedObject_CaptureLost); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task DoDragDrop(PointerEventArgs triggerEvent, object? value) |
||||||
|
{ |
||||||
|
var data = new DataObject(); |
||||||
|
data.Set(DataFormat, value!); |
||||||
|
|
||||||
|
var effect = DragDropEffects.None; |
||||||
|
|
||||||
|
if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Alt)) |
||||||
|
{ |
||||||
|
effect |= DragDropEffects.Link; |
||||||
|
} |
||||||
|
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Shift)) |
||||||
|
{ |
||||||
|
effect |= DragDropEffects.Move; |
||||||
|
} |
||||||
|
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Control)) |
||||||
|
{ |
||||||
|
effect |= DragDropEffects.Copy; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
effect |= DragDropEffects.Move; |
||||||
|
} |
||||||
|
|
||||||
|
await DragDrop.DoDragDrop(triggerEvent, data, effect); |
||||||
|
} |
||||||
|
|
||||||
|
private void Released() |
||||||
|
{ |
||||||
|
_triggerEvent = null; |
||||||
|
_lock = false; |
||||||
|
} |
||||||
|
|
||||||
|
private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e) |
||||||
|
{ |
||||||
|
var properties = e.GetCurrentPoint(AssociatedObject).Properties; |
||||||
|
if (properties.IsLeftButtonPressed) |
||||||
|
{ |
||||||
|
if (e.Source is Control control && AssociatedObject?.DataContext == control.DataContext) |
||||||
|
{ |
||||||
|
_dragStartPoint = e.GetPosition(null); |
||||||
|
_triggerEvent = e; |
||||||
|
_lock = true; |
||||||
|
_captured = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void AssociatedObject_PointerReleased(object? sender, PointerReleasedEventArgs e) |
||||||
|
{ |
||||||
|
if (_captured) |
||||||
|
{ |
||||||
|
if (e.InitialPressMouseButton == MouseButton.Left && _triggerEvent is { }) |
||||||
|
{ |
||||||
|
Released(); |
||||||
|
} |
||||||
|
|
||||||
|
_captured = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e) |
||||||
|
{ |
||||||
|
var properties = e.GetCurrentPoint(AssociatedObject).Properties; |
||||||
|
if (_captured && properties.IsLeftButtonPressed && _triggerEvent is { }) |
||||||
|
{ |
||||||
|
var point = e.GetPosition(null); |
||||||
|
var diff = _dragStartPoint - point; |
||||||
|
var horizontalDragThreshold = HorizontalDragThreshold; |
||||||
|
var verticalDragThreshold = VerticalDragThreshold; |
||||||
|
|
||||||
|
if (Math.Abs(diff.X) > horizontalDragThreshold || Math.Abs(diff.Y) > verticalDragThreshold) |
||||||
|
{ |
||||||
|
if (_lock) |
||||||
|
{ |
||||||
|
_lock = false; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
var context = Context ?? AssociatedObject?.DataContext; |
||||||
|
|
||||||
|
Handler?.BeforeDragDrop(sender, _triggerEvent, context); |
||||||
|
|
||||||
|
await DoDragDrop(_triggerEvent, context); |
||||||
|
|
||||||
|
Handler?.AfterDragDrop(sender, _triggerEvent, context); |
||||||
|
|
||||||
|
_triggerEvent = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void AssociatedObject_CaptureLost(object? sender, PointerCaptureLostEventArgs e) |
||||||
|
{ |
||||||
|
Released(); |
||||||
|
_captured = false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
public class OpenArtCustomNode |
||||||
|
{ |
||||||
|
public required string Title { get; set; } |
||||||
|
public List<string> Children { get; set; } = []; |
||||||
|
public bool IsInstalled { get; set; } |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using System.Text.Json.Serialization; |
||||||
|
using Avalonia.Platform.Storage; |
||||||
|
using StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
public class OpenArtMetadata |
||||||
|
{ |
||||||
|
[JsonPropertyName("sm_workflow_data")] |
||||||
|
public OpenArtSearchResult? Workflow { get; set; } |
||||||
|
|
||||||
|
[JsonIgnore] |
||||||
|
public string? FirstThumbnail => Workflow?.Thumbnails?.Select(x => x.Url).FirstOrDefault()?.ToString(); |
||||||
|
|
||||||
|
[JsonIgnore] |
||||||
|
public List<IStorageFile>? FilePath { get; set; } |
||||||
|
|
||||||
|
[JsonIgnore] |
||||||
|
public bool HasMetadata => Workflow?.Creator != null; |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using StabilityMatrix.Core.Models.FileInterfaces; |
||||||
|
using StabilityMatrix.Core.Models.PackageModification; |
||||||
|
using StabilityMatrix.Core.Models.Progress; |
||||||
|
using StabilityMatrix.Core.Python; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models.PackageSteps; |
||||||
|
|
||||||
|
public class UnpackSiteCustomizeStep(DirectoryPath venvPath) : IPackageStep |
||||||
|
{ |
||||||
|
public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null) |
||||||
|
{ |
||||||
|
var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath); |
||||||
|
var file = sitePackages.JoinFile("sitecustomize.py"); |
||||||
|
file.Directory?.Create(); |
||||||
|
await Assets.PyScriptSiteCustomize.ExtractTo(file); |
||||||
|
} |
||||||
|
|
||||||
|
public string ProgressTitle => "Unpacking prerequisites..."; |
||||||
|
} |
@ -0,0 +1,185 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.ComponentModel; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using AsyncAwaitBestPractices; |
||||||
|
using Avalonia.Controls; |
||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||||
|
using StabilityMatrix.Avalonia.Views.Dialogs; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
using StabilityMatrix.Core.Helper; |
||||||
|
using StabilityMatrix.Core.Helper.Factory; |
||||||
|
using StabilityMatrix.Core.Models; |
||||||
|
using StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
using StabilityMatrix.Core.Models.Packages.Extensions; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||||
|
|
||||||
|
[View(typeof(OpenArtWorkflowDialog))] |
||||||
|
[ManagedService] |
||||||
|
[Transient] |
||||||
|
public partial class OpenArtWorkflowViewModel( |
||||||
|
ISettingsManager settingsManager, |
||||||
|
IPackageFactory packageFactory |
||||||
|
) : ContentDialogViewModelBase |
||||||
|
{ |
||||||
|
public required OpenArtSearchResult Workflow { get; init; } |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private ObservableCollection<OpenArtCustomNode> customNodes = []; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string prunedDescription = string.Empty; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool installRequiredNodes = true; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private InstalledPackage? selectedPackage; |
||||||
|
|
||||||
|
public PackagePair? SelectedPackagePair => |
||||||
|
SelectedPackage is { } package ? packageFactory.GetPackagePair(package) : null; |
||||||
|
|
||||||
|
public List<InstalledPackage> AvailablePackages => |
||||||
|
settingsManager |
||||||
|
.Settings.InstalledPackages.Where(package => package.PackageName == "ComfyUI") |
||||||
|
.ToList(); |
||||||
|
|
||||||
|
public List<PackageExtension> MissingNodes { get; } = []; |
||||||
|
|
||||||
|
public override async Task OnLoadedAsync() |
||||||
|
{ |
||||||
|
if (Design.IsDesignMode) |
||||||
|
return; |
||||||
|
|
||||||
|
if (settingsManager.Settings.PreferredWorkflowPackage is { } preferredPackage) |
||||||
|
{ |
||||||
|
SelectedPackage = preferredPackage; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SelectedPackage = AvailablePackages.FirstOrDefault(); |
||||||
|
} |
||||||
|
|
||||||
|
if (SelectedPackage == null) |
||||||
|
{ |
||||||
|
InstallRequiredNodes = false; |
||||||
|
} |
||||||
|
|
||||||
|
CustomNodes = new ObservableCollection<OpenArtCustomNode>( |
||||||
|
await ParseNodes(Workflow.NodesIndex.ToList()) |
||||||
|
); |
||||||
|
PrunedDescription = Utilities.RemoveHtml(Workflow.Description); |
||||||
|
} |
||||||
|
|
||||||
|
partial void OnSelectedPackageChanged(InstalledPackage? oldValue, InstalledPackage? newValue) |
||||||
|
{ |
||||||
|
if (oldValue is null) |
||||||
|
return; |
||||||
|
|
||||||
|
settingsManager.Transaction(settings => |
||||||
|
{ |
||||||
|
settings.PreferredWorkflowPackage = newValue; |
||||||
|
}); |
||||||
|
|
||||||
|
OnLoadedAsync().SafeFireAndForget(); |
||||||
|
} |
||||||
|
|
||||||
|
[Localizable(false)] |
||||||
|
private async Task<List<OpenArtCustomNode>> ParseNodes(List<string> nodes) |
||||||
|
{ |
||||||
|
var indexOfFirstDot = nodes.IndexOf("."); |
||||||
|
if (indexOfFirstDot != -1) |
||||||
|
{ |
||||||
|
nodes = nodes[(indexOfFirstDot + 1)..]; |
||||||
|
} |
||||||
|
|
||||||
|
var installedNodesNames = new HashSet<string>(); |
||||||
|
var nameToManifestNodes = new Dictionary<string, PackageExtension>(); |
||||||
|
|
||||||
|
var packagePair = SelectedPackagePair; |
||||||
|
|
||||||
|
if (packagePair?.BasePackage.ExtensionManager is { } extensionManager) |
||||||
|
{ |
||||||
|
var installedNodes = ( |
||||||
|
await extensionManager.GetInstalledExtensionsLiteAsync(packagePair.InstalledPackage) |
||||||
|
).ToList(); |
||||||
|
|
||||||
|
var manifestExtensionsMap = await extensionManager.GetManifestExtensionsMapAsync( |
||||||
|
extensionManager.GetManifests(packagePair.InstalledPackage) |
||||||
|
); |
||||||
|
|
||||||
|
// Add manifestExtensions definition to installedNodes if matching git repository url |
||||||
|
installedNodes = installedNodes |
||||||
|
.Select(installedNode => |
||||||
|
{ |
||||||
|
if ( |
||||||
|
installedNode.GitRepositoryUrl is not null |
||||||
|
&& manifestExtensionsMap.TryGetValue( |
||||||
|
installedNode.GitRepositoryUrl, |
||||||
|
out var manifestExtension |
||||||
|
) |
||||||
|
) |
||||||
|
{ |
||||||
|
installedNode = installedNode with { Definition = manifestExtension }; |
||||||
|
} |
||||||
|
|
||||||
|
return installedNode; |
||||||
|
}) |
||||||
|
.ToList(); |
||||||
|
|
||||||
|
// There may be duplicate titles, deduplicate by using the first one |
||||||
|
nameToManifestNodes = manifestExtensionsMap |
||||||
|
.GroupBy(x => x.Value.Title) |
||||||
|
.ToDictionary(x => x.Key, x => x.First().Value); |
||||||
|
|
||||||
|
installedNodesNames = installedNodes.Select(x => x.Title).ToHashSet(); |
||||||
|
} |
||||||
|
|
||||||
|
var sections = new List<OpenArtCustomNode>(); |
||||||
|
OpenArtCustomNode? currentSection = null; |
||||||
|
|
||||||
|
foreach (var node in nodes) |
||||||
|
{ |
||||||
|
if (node is "." or ",") |
||||||
|
{ |
||||||
|
currentSection = null; // End of the current section |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (currentSection == null) |
||||||
|
{ |
||||||
|
currentSection = new OpenArtCustomNode |
||||||
|
{ |
||||||
|
Title = node, |
||||||
|
IsInstalled = installedNodesNames.Contains(node) |
||||||
|
}; |
||||||
|
|
||||||
|
// Add missing nodes to the list |
||||||
|
if ( |
||||||
|
!currentSection.IsInstalled && nameToManifestNodes.TryGetValue(node, out var manifestNode) |
||||||
|
) |
||||||
|
{ |
||||||
|
MissingNodes.Add(manifestNode); |
||||||
|
} |
||||||
|
|
||||||
|
sections.Add(currentSection); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
currentSection.Children.Add(node); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (sections.FirstOrDefault(x => x.Title == "ComfyUI") != null) |
||||||
|
{ |
||||||
|
sections = sections.Where(x => x.Title != "ComfyUI").ToList(); |
||||||
|
} |
||||||
|
|
||||||
|
return sections; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,173 @@ |
|||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Text.Json; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using AsyncAwaitBestPractices; |
||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Platform.Storage; |
||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using CommunityToolkit.Mvvm.Input; |
||||||
|
using DynamicData; |
||||||
|
using DynamicData.Binding; |
||||||
|
using FluentAvalonia.UI.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Languages; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
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.Api.OpenArt; |
||||||
|
using StabilityMatrix.Core.Processes; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.ViewModels; |
||||||
|
|
||||||
|
[View(typeof(InstalledWorkflowsPage))] |
||||||
|
[Singleton] |
||||||
|
public partial class InstalledWorkflowsViewModel( |
||||||
|
ISettingsManager settingsManager, |
||||||
|
INotificationService notificationService |
||||||
|
) : TabViewModelBase, IDisposable |
||||||
|
{ |
||||||
|
public override string Header => Resources.TabLabel_InstalledWorkflows; |
||||||
|
|
||||||
|
private readonly SourceCache<OpenArtMetadata, string> workflowsCache = |
||||||
|
new(x => x.Workflow?.Id ?? Guid.NewGuid().ToString()); |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private IObservableCollection<OpenArtMetadata> displayedWorkflows = |
||||||
|
new ObservableCollectionExtended<OpenArtMetadata>(); |
||||||
|
|
||||||
|
protected override async Task OnInitialLoadedAsync() |
||||||
|
{ |
||||||
|
await base.OnInitialLoadedAsync(); |
||||||
|
|
||||||
|
workflowsCache.Connect().DeferUntilLoaded().Bind(DisplayedWorkflows).Subscribe(); |
||||||
|
|
||||||
|
if (Design.IsDesignMode) |
||||||
|
return; |
||||||
|
|
||||||
|
await LoadInstalledWorkflowsAsync(); |
||||||
|
EventManager.Instance.WorkflowInstalled += OnWorkflowInstalled; |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task LoadInstalledWorkflowsAsync() |
||||||
|
{ |
||||||
|
workflowsCache.Clear(); |
||||||
|
|
||||||
|
if (!Directory.Exists(settingsManager.WorkflowDirectory)) |
||||||
|
{ |
||||||
|
Directory.CreateDirectory(settingsManager.WorkflowDirectory); |
||||||
|
} |
||||||
|
|
||||||
|
foreach ( |
||||||
|
var workflowPath in Directory.EnumerateFiles( |
||||||
|
settingsManager.WorkflowDirectory, |
||||||
|
"*.json", |
||||||
|
SearchOption.AllDirectories |
||||||
|
) |
||||||
|
) |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
var json = await File.ReadAllTextAsync(workflowPath); |
||||||
|
var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json); |
||||||
|
|
||||||
|
if (metadata?.Workflow == null) |
||||||
|
{ |
||||||
|
metadata = new OpenArtMetadata |
||||||
|
{ |
||||||
|
Workflow = new OpenArtSearchResult |
||||||
|
{ |
||||||
|
Id = Guid.NewGuid().ToString(), |
||||||
|
Name = Path.GetFileNameWithoutExtension(workflowPath) |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
metadata.FilePath = [await App.StorageProvider.TryGetFileFromPathAsync(workflowPath)]; |
||||||
|
workflowsCache.AddOrUpdate(metadata); |
||||||
|
} |
||||||
|
catch (Exception e) |
||||||
|
{ |
||||||
|
Console.WriteLine(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task OpenInExplorer(OpenArtMetadata metadata) |
||||||
|
{ |
||||||
|
if (metadata.FilePath == null) |
||||||
|
return; |
||||||
|
|
||||||
|
var path = metadata.FilePath.FirstOrDefault()?.Path.ToString(); |
||||||
|
if (string.IsNullOrWhiteSpace(path)) |
||||||
|
return; |
||||||
|
|
||||||
|
await ProcessRunner.OpenFileBrowser(path); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private void OpenOnOpenArt(OpenArtMetadata metadata) |
||||||
|
{ |
||||||
|
if (metadata.Workflow == null) |
||||||
|
return; |
||||||
|
|
||||||
|
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{metadata.Workflow.Id}"); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task DeleteAsync(OpenArtMetadata metadata) |
||||||
|
{ |
||||||
|
var confirmationDialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
Title = Resources.Label_AreYouSure, |
||||||
|
Content = Resources.Label_ActionCannotBeUndone, |
||||||
|
PrimaryButtonText = Resources.Action_Delete, |
||||||
|
SecondaryButtonText = Resources.Action_Cancel, |
||||||
|
DefaultButton = ContentDialogButton.Primary, |
||||||
|
IsSecondaryButtonEnabled = true, |
||||||
|
}; |
||||||
|
var dialogResult = await confirmationDialog.ShowAsync(); |
||||||
|
if (dialogResult != ContentDialogResult.Primary) |
||||||
|
return; |
||||||
|
|
||||||
|
await using var delay = new MinimumDelay(200, 500); |
||||||
|
|
||||||
|
var path = metadata?.FilePath?.FirstOrDefault()?.Path.ToString().Replace("file:///", ""); |
||||||
|
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path)) |
||||||
|
{ |
||||||
|
await notificationService.TryAsync( |
||||||
|
Task.Run(() => File.Delete(path)), |
||||||
|
message: "Error deleting workflow" |
||||||
|
); |
||||||
|
|
||||||
|
var id = metadata?.Workflow?.Id; |
||||||
|
if (!string.IsNullOrWhiteSpace(id)) |
||||||
|
{ |
||||||
|
workflowsCache.Remove(id); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
notificationService.Show( |
||||||
|
Resources.Label_WorkflowDeleted, |
||||||
|
string.Format(Resources.Label_WorkflowDeletedSuccessfully, metadata?.Workflow?.Name) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private void OnWorkflowInstalled(object? sender, EventArgs e) |
||||||
|
{ |
||||||
|
LoadInstalledWorkflowsAsync().SafeFireAndForget(); |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
workflowsCache.Dispose(); |
||||||
|
EventManager.Instance.WorkflowInstalled -= OnWorkflowInstalled; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,348 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.ComponentModel; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using AsyncAwaitBestPractices; |
||||||
|
using Avalonia.Controls.Notifications; |
||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using CommunityToolkit.Mvvm.Input; |
||||||
|
using DynamicData; |
||||||
|
using DynamicData.Binding; |
||||||
|
using FluentAvalonia.UI.Controls; |
||||||
|
using Refit; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Languages; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
using StabilityMatrix.Avalonia.Services; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||||
|
using StabilityMatrix.Avalonia.Views; |
||||||
|
using StabilityMatrix.Core.Api; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
using StabilityMatrix.Core.Helper; |
||||||
|
using StabilityMatrix.Core.Helper.Factory; |
||||||
|
using StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
using StabilityMatrix.Core.Models.PackageModification; |
||||||
|
using StabilityMatrix.Core.Processes; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
using Resources = StabilityMatrix.Avalonia.Languages.Resources; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.ViewModels; |
||||||
|
|
||||||
|
[View(typeof(OpenArtBrowserPage))] |
||||||
|
[Singleton] |
||||||
|
public partial class OpenArtBrowserViewModel( |
||||||
|
IOpenArtApi openArtApi, |
||||||
|
INotificationService notificationService, |
||||||
|
ISettingsManager settingsManager, |
||||||
|
IPackageFactory packageFactory, |
||||||
|
ServiceManager<ViewModelBase> vmFactory |
||||||
|
) : TabViewModelBase, IInfinitelyScroll |
||||||
|
{ |
||||||
|
private const int PageSize = 20; |
||||||
|
|
||||||
|
public override string Header => Resources.Label_OpenArtBrowser; |
||||||
|
|
||||||
|
private readonly SourceCache<OpenArtSearchResult, string> searchResultsCache = new(x => x.Id); |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
[NotifyPropertyChangedFor(nameof(PageCount), nameof(CanGoBack), nameof(CanGoForward), nameof(CanGoToEnd))] |
||||||
|
private OpenArtSearchResponse? latestSearchResponse; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private IObservableCollection<OpenArtSearchResult> searchResults = |
||||||
|
new ObservableCollectionExtended<OpenArtSearchResult>(); |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string searchQuery = string.Empty; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool isLoading; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
[NotifyPropertyChangedFor(nameof(InternalPageNumber), nameof(CanGoBack))] |
||||||
|
private int displayedPageNumber = 1; |
||||||
|
|
||||||
|
public int InternalPageNumber => DisplayedPageNumber - 1; |
||||||
|
|
||||||
|
public int PageCount => |
||||||
|
Math.Max( |
||||||
|
1, |
||||||
|
Convert.ToInt32(Math.Ceiling((LatestSearchResponse?.Total ?? 0) / Convert.ToDouble(PageSize))) |
||||||
|
); |
||||||
|
|
||||||
|
public bool CanGoBack => |
||||||
|
string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && InternalPageNumber > 0; |
||||||
|
|
||||||
|
public bool CanGoForward => |
||||||
|
!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) || PageCount > InternalPageNumber + 1; |
||||||
|
|
||||||
|
public bool CanGoToEnd => |
||||||
|
string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && PageCount > InternalPageNumber + 1; |
||||||
|
|
||||||
|
public IEnumerable<string> AllSortModes => ["Trending", "Latest", "Most Downloaded", "Most Liked"]; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string? selectedSortMode; |
||||||
|
|
||||||
|
protected override void OnInitialLoaded() |
||||||
|
{ |
||||||
|
searchResultsCache.Connect().DeferUntilLoaded().Bind(SearchResults).Subscribe(); |
||||||
|
SelectedSortMode = AllSortModes.First(); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task FirstPage() |
||||||
|
{ |
||||||
|
DisplayedPageNumber = 1; |
||||||
|
searchResultsCache.Clear(); |
||||||
|
|
||||||
|
await DoSearch(); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task PreviousPage() |
||||||
|
{ |
||||||
|
DisplayedPageNumber--; |
||||||
|
searchResultsCache.Clear(); |
||||||
|
|
||||||
|
await DoSearch(InternalPageNumber); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task NextPage() |
||||||
|
{ |
||||||
|
if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor)) |
||||||
|
{ |
||||||
|
DisplayedPageNumber++; |
||||||
|
} |
||||||
|
|
||||||
|
searchResultsCache.Clear(); |
||||||
|
await DoSearch(InternalPageNumber); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task LastPage() |
||||||
|
{ |
||||||
|
if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor)) |
||||||
|
{ |
||||||
|
DisplayedPageNumber = PageCount; |
||||||
|
} |
||||||
|
|
||||||
|
searchResultsCache.Clear(); |
||||||
|
await DoSearch(PageCount - 1); |
||||||
|
} |
||||||
|
|
||||||
|
[Localizable(false)] |
||||||
|
[RelayCommand] |
||||||
|
private void OpenModel(OpenArtSearchResult workflow) |
||||||
|
{ |
||||||
|
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{workflow.Id}"); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task SearchButton() |
||||||
|
{ |
||||||
|
DisplayedPageNumber = 1; |
||||||
|
LatestSearchResponse = null; |
||||||
|
searchResultsCache.Clear(); |
||||||
|
|
||||||
|
await DoSearch(); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task OpenWorkflow(OpenArtSearchResult workflow) |
||||||
|
{ |
||||||
|
var vm = new OpenArtWorkflowViewModel(settingsManager, packageFactory) { Workflow = workflow }; |
||||||
|
|
||||||
|
var dialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
IsPrimaryButtonEnabled = true, |
||||||
|
IsSecondaryButtonEnabled = true, |
||||||
|
PrimaryButtonText = Resources.Action_Import, |
||||||
|
SecondaryButtonText = Resources.Action_Cancel, |
||||||
|
DefaultButton = ContentDialogButton.Primary, |
||||||
|
IsFooterVisible = true, |
||||||
|
MaxDialogWidth = 750, |
||||||
|
MaxDialogHeight = 850, |
||||||
|
CloseOnClickOutside = true, |
||||||
|
Content = vm |
||||||
|
}; |
||||||
|
|
||||||
|
var result = await dialog.ShowAsync(); |
||||||
|
|
||||||
|
if (result != ContentDialogResult.Primary) |
||||||
|
return; |
||||||
|
|
||||||
|
List<IPackageStep> steps = |
||||||
|
[ |
||||||
|
new DownloadOpenArtWorkflowStep(openArtApi, vm.Workflow, settingsManager) |
||||||
|
]; |
||||||
|
|
||||||
|
// Add install steps if missing nodes and preferred |
||||||
|
if ( |
||||||
|
vm is |
||||||
|
{ |
||||||
|
InstallRequiredNodes: true, |
||||||
|
MissingNodes: { Count: > 0 } missingNodes, |
||||||
|
SelectedPackage: not null, |
||||||
|
SelectedPackagePair: not null |
||||||
|
} |
||||||
|
) |
||||||
|
{ |
||||||
|
var extensionManager = vm.SelectedPackagePair.BasePackage.ExtensionManager!; |
||||||
|
|
||||||
|
steps.AddRange( |
||||||
|
missingNodes.Select( |
||||||
|
extension => |
||||||
|
new InstallExtensionStep( |
||||||
|
extensionManager, |
||||||
|
vm.SelectedPackagePair.InstalledPackage, |
||||||
|
extension |
||||||
|
) |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
var runner = new PackageModificationRunner |
||||||
|
{ |
||||||
|
ShowDialogOnStart = true, |
||||||
|
ModificationCompleteTitle = Resources.Label_WorkflowImported, |
||||||
|
ModificationCompleteMessage = Resources.Label_FinishedImportingWorkflow |
||||||
|
}; |
||||||
|
EventManager.Instance.OnPackageInstallProgressAdded(runner); |
||||||
|
|
||||||
|
await runner.ExecuteSteps(steps); |
||||||
|
|
||||||
|
notificationService.Show( |
||||||
|
Resources.Label_WorkflowImported, |
||||||
|
Resources.Label_WorkflowImportComplete, |
||||||
|
NotificationType.Success |
||||||
|
); |
||||||
|
|
||||||
|
EventManager.Instance.OnWorkflowInstalled(); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private void OpenOnOpenArt(OpenArtSearchResult? workflow) |
||||||
|
{ |
||||||
|
if (workflow?.Id == null) |
||||||
|
return; |
||||||
|
|
||||||
|
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{workflow.Id}"); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task DoSearch(int page = 0) |
||||||
|
{ |
||||||
|
IsLoading = true; |
||||||
|
|
||||||
|
try |
||||||
|
{ |
||||||
|
OpenArtSearchResponse? response = null; |
||||||
|
if (string.IsNullOrWhiteSpace(SearchQuery)) |
||||||
|
{ |
||||||
|
var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) }; |
||||||
|
if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor)) |
||||||
|
{ |
||||||
|
request.Cursor = LatestSearchResponse.NextCursor; |
||||||
|
} |
||||||
|
|
||||||
|
response = await openArtApi.GetFeedAsync(request); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
response = await openArtApi.SearchAsync( |
||||||
|
new OpenArtSearchRequest |
||||||
|
{ |
||||||
|
Keyword = SearchQuery, |
||||||
|
PageSize = PageSize, |
||||||
|
CurrentPage = page |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var item in response.Items) |
||||||
|
{ |
||||||
|
searchResultsCache.AddOrUpdate(item); |
||||||
|
} |
||||||
|
|
||||||
|
LatestSearchResponse = response; |
||||||
|
} |
||||||
|
catch (ApiException e) |
||||||
|
{ |
||||||
|
notificationService.Show(Resources.Label_ErrorRetrievingWorkflows, e.Message); |
||||||
|
} |
||||||
|
finally |
||||||
|
{ |
||||||
|
IsLoading = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
partial void OnSelectedSortModeChanged(string? value) |
||||||
|
{ |
||||||
|
if (value is null || SearchResults.Count == 0) |
||||||
|
return; |
||||||
|
|
||||||
|
searchResultsCache.Clear(); |
||||||
|
LatestSearchResponse = null; |
||||||
|
|
||||||
|
DoSearch().SafeFireAndForget(); |
||||||
|
} |
||||||
|
|
||||||
|
public async Task LoadNextPageAsync() |
||||||
|
{ |
||||||
|
if (!CanGoForward) |
||||||
|
return; |
||||||
|
|
||||||
|
try |
||||||
|
{ |
||||||
|
OpenArtSearchResponse? response = null; |
||||||
|
if (string.IsNullOrWhiteSpace(SearchQuery)) |
||||||
|
{ |
||||||
|
var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) }; |
||||||
|
if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor)) |
||||||
|
{ |
||||||
|
request.Cursor = LatestSearchResponse.NextCursor; |
||||||
|
} |
||||||
|
|
||||||
|
response = await openArtApi.GetFeedAsync(request); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
DisplayedPageNumber++; |
||||||
|
response = await openArtApi.SearchAsync( |
||||||
|
new OpenArtSearchRequest |
||||||
|
{ |
||||||
|
Keyword = SearchQuery, |
||||||
|
PageSize = PageSize, |
||||||
|
CurrentPage = InternalPageNumber |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var item in response.Items) |
||||||
|
{ |
||||||
|
searchResultsCache.AddOrUpdate(item); |
||||||
|
} |
||||||
|
|
||||||
|
LatestSearchResponse = response; |
||||||
|
} |
||||||
|
catch (ApiException e) |
||||||
|
{ |
||||||
|
notificationService.Show("Unable to load the next page", e.Message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static string GetSortMode(string? sortMode) |
||||||
|
{ |
||||||
|
return sortMode switch |
||||||
|
{ |
||||||
|
"Trending" => "trending", |
||||||
|
"Latest" => "latest", |
||||||
|
"Most Downloaded" => "most_downloaded", |
||||||
|
"Most Liked" => "most_liked", |
||||||
|
_ => "trending" |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using Avalonia.Controls; |
||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using FluentAvalonia.UI.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Languages; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||||
|
using StabilityMatrix.Avalonia.Views; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.ViewModels; |
||||||
|
|
||||||
|
[View(typeof(WorkflowsPage))] |
||||||
|
[Singleton] |
||||||
|
public partial class WorkflowsPageViewModel : PageViewModelBase |
||||||
|
{ |
||||||
|
public override string Title => Resources.Label_Workflows; |
||||||
|
public override IconSource IconSource => new FASymbolIconSource { Symbol = "fa-solid fa-circle-nodes" }; |
||||||
|
|
||||||
|
public IReadOnlyList<TabItem> Pages { get; } |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private TabItem? selectedPage; |
||||||
|
|
||||||
|
/// <inheritdoc/> |
||||||
|
public WorkflowsPageViewModel( |
||||||
|
OpenArtBrowserViewModel openArtBrowserViewModel, |
||||||
|
InstalledWorkflowsViewModel installedWorkflowsViewModel |
||||||
|
) |
||||||
|
{ |
||||||
|
Pages = new List<TabItem>( |
||||||
|
new List<TabViewModelBase>([openArtBrowserViewModel, installedWorkflowsViewModel]).Select( |
||||||
|
vm => new TabItem { Header = vm.Header, Content = vm } |
||||||
|
) |
||||||
|
); |
||||||
|
SelectedPage = Pages.FirstOrDefault(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
<controls:UserControlBase |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.OpenArtWorkflowDialog" |
||||||
|
xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" |
||||||
|
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||||
|
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||||
|
xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||||
|
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models" |
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime" |
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||||
|
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls" |
||||||
|
d:DataContext="{x:Static designData:DesignData.OpenArtWorkflowViewModel}" |
||||||
|
d:DesignHeight="650" |
||||||
|
d:DesignWidth="600" |
||||||
|
x:DataType="dialogs:OpenArtWorkflowViewModel" |
||||||
|
mc:Ignorable="d"> |
||||||
|
<Grid |
||||||
|
Width="600" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
ColumnDefinitions="*, 2*" |
||||||
|
RowDefinitions="Auto, Auto, Auto, Auto, Auto"> |
||||||
|
<TextBlock |
||||||
|
Grid.Row="0" |
||||||
|
Grid.Column="0" |
||||||
|
Grid.ColumnSpan="2" |
||||||
|
Margin="8,8,0,4" |
||||||
|
FontSize="20" |
||||||
|
TextAlignment="Left" |
||||||
|
ToolTip.Tip="{Binding Workflow.Name}"> |
||||||
|
<Run Text="{Binding Workflow.Name}" /> |
||||||
|
<Run Text="- by" /> |
||||||
|
<Run Text="{Binding Workflow.Creator.Name}" /> |
||||||
|
</TextBlock> |
||||||
|
|
||||||
|
<labs:AsyncImage |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="0" |
||||||
|
Height="300" |
||||||
|
Margin="8" |
||||||
|
CornerRadius="8" |
||||||
|
Source="{Binding Workflow.Thumbnails[0].Url}" |
||||||
|
Stretch="UniformToFill" /> |
||||||
|
|
||||||
|
<controls:Card |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="1" |
||||||
|
Margin="8" |
||||||
|
VerticalAlignment="Top"> |
||||||
|
<ScrollViewer MaxHeight="270"> |
||||||
|
<TextBlock |
||||||
|
Margin="4" |
||||||
|
Text="{Binding PrunedDescription}" |
||||||
|
TextWrapping="Wrap" /> |
||||||
|
</ScrollViewer> |
||||||
|
</controls:Card> |
||||||
|
|
||||||
|
<Expander |
||||||
|
Grid.Row="3" |
||||||
|
Grid.Column="0" |
||||||
|
Grid.ColumnSpan="2" |
||||||
|
Margin="8,8" |
||||||
|
ExpandDirection="Down" |
||||||
|
Header="{x:Static lang:Resources.Label_NodeDetails}"> |
||||||
|
<ScrollViewer MaxHeight="225"> |
||||||
|
<ItemsControl ItemsSource="{Binding CustomNodes}"> |
||||||
|
<ItemsControl.ItemsPanel> |
||||||
|
<ItemsPanelTemplate> |
||||||
|
<StackPanel Orientation="Vertical" Spacing="4" /> |
||||||
|
</ItemsPanelTemplate> |
||||||
|
</ItemsControl.ItemsPanel> |
||||||
|
<ItemsControl.ItemTemplate> |
||||||
|
<DataTemplate DataType="{x:Type models:OpenArtCustomNode}"> |
||||||
|
<StackPanel Orientation="Vertical"> |
||||||
|
<StackPanel Orientation="Horizontal"> |
||||||
|
<TextBlock |
||||||
|
FontSize="16" |
||||||
|
FontWeight="SemiBold" |
||||||
|
Text="{Binding Title}" /> |
||||||
|
<avalonia:Icon |
||||||
|
Margin="4" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Foreground="Lime" |
||||||
|
IsVisible="{Binding IsInstalled}" |
||||||
|
Value="fa-solid fa-circle-check" /> |
||||||
|
</StackPanel> |
||||||
|
<ItemsControl Margin="0,4" ItemsSource="{Binding Children}"> |
||||||
|
<ItemsControl.ItemsPanel> |
||||||
|
<ItemsPanelTemplate> |
||||||
|
<StackPanel Orientation="Vertical" Spacing="4" /> |
||||||
|
</ItemsPanelTemplate> |
||||||
|
</ItemsControl.ItemsPanel> |
||||||
|
<ItemsControl.ItemTemplate> |
||||||
|
<DataTemplate DataType="{x:Type system:String}"> |
||||||
|
<StackPanel Orientation="Vertical"> |
||||||
|
<TextBlock Margin="4,0,0,0" Text="{Binding ., StringFormat={} - {0}}" /> |
||||||
|
</StackPanel> |
||||||
|
</DataTemplate> |
||||||
|
</ItemsControl.ItemTemplate> |
||||||
|
</ItemsControl> |
||||||
|
</StackPanel> |
||||||
|
</DataTemplate> |
||||||
|
</ItemsControl.ItemTemplate> |
||||||
|
</ItemsControl> |
||||||
|
</ScrollViewer> |
||||||
|
</Expander> |
||||||
|
|
||||||
|
<Expander |
||||||
|
Grid.Row="4" |
||||||
|
Grid.Column="0" |
||||||
|
Grid.ColumnSpan="2" |
||||||
|
Margin="8" |
||||||
|
Header="Options"> |
||||||
|
<StackPanel Spacing="4"> |
||||||
|
<ui:SettingsExpanderItem Content="Install Required Nodes"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<CheckBox IsChecked="{Binding InstallRequiredNodes}" |
||||||
|
IsEnabled="{Binding AvailablePackages.Count}"/> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
<ui:SettingsExpanderItem Content="Target Package"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<ComboBox |
||||||
|
DisplayMemberBinding="{Binding DisplayName}" |
||||||
|
ItemsSource="{Binding AvailablePackages}" |
||||||
|
SelectedItem="{Binding SelectedPackage}"/> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
</StackPanel> |
||||||
|
</Expander> |
||||||
|
|
||||||
|
</Grid> |
||||||
|
</controls:UserControlBase> |
@ -0,0 +1,13 @@ |
|||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Views.Dialogs; |
||||||
|
|
||||||
|
[Transient] |
||||||
|
public partial class OpenArtWorkflowDialog : UserControlBase |
||||||
|
{ |
||||||
|
public OpenArtWorkflowDialog() |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,254 @@ |
|||||||
|
<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:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||||
|
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" |
||||||
|
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||||
|
xmlns:openArt="clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt" |
||||||
|
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||||
|
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" |
||||||
|
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls" |
||||||
|
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models" |
||||||
|
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" |
||||||
|
xmlns:fluent="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent" |
||||||
|
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia" |
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||||
|
d:DataContext="{x:Static designData:DesignData.InstalledWorkflowsViewModel}" |
||||||
|
x:DataType="viewModels:InstalledWorkflowsViewModel" |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.InstalledWorkflowsPage"> |
||||||
|
<UserControl.Styles> |
||||||
|
<Style Selector="Border#HoverBorder"> |
||||||
|
<Setter Property="Transitions"> |
||||||
|
<Transitions> |
||||||
|
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237" /> |
||||||
|
</Transitions> |
||||||
|
</Setter> |
||||||
|
|
||||||
|
<Style Selector="^ labs|AsyncImage"> |
||||||
|
<Setter Property="Transitions"> |
||||||
|
<Transitions> |
||||||
|
<TransformOperationsTransition Property="RenderTransform" |
||||||
|
Duration="0:0:0.237"> |
||||||
|
<TransformOperationsTransition.Easing> |
||||||
|
<QuadraticEaseInOut /> |
||||||
|
</TransformOperationsTransition.Easing> |
||||||
|
</TransformOperationsTransition> |
||||||
|
</Transitions> |
||||||
|
</Setter> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000" /> |
||||||
|
<Setter Property="Cursor" Value="Hand" /> |
||||||
|
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||||
|
<Setter Property="CornerRadius" Value="12" /> |
||||||
|
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^ Border#ModelCardBottom"> |
||||||
|
<Setter Property="Background" Value="#CC000000" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:not(:pointerover)"> |
||||||
|
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000" /> |
||||||
|
<Setter Property="Cursor" Value="Arrow" /> |
||||||
|
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||||
|
<Setter Property="CornerRadius" Value="8" /> |
||||||
|
<Setter Property="RenderTransform" Value="scale(1, 1)" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^ Border#ModelCardBottom"> |
||||||
|
<Setter Property="Background" Value="#99000000" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</UserControl.Styles> |
||||||
|
|
||||||
|
<UserControl.Resources> |
||||||
|
<input:StandardUICommand |
||||||
|
x:Key="OpenInExplorerCommand" |
||||||
|
Command="{Binding OpenInExplorerCommand}" /> |
||||||
|
<input:StandardUICommand |
||||||
|
x:Key="OpenOnOpenArtCommand" |
||||||
|
Command="{Binding OpenOnOpenArtCommand}" /> |
||||||
|
<input:StandardUICommand |
||||||
|
x:Key="DeleteCommand" |
||||||
|
Command="{Binding DeleteCommand}" /> |
||||||
|
</UserControl.Resources> |
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto, *"> |
||||||
|
<controls1:CommandBar Grid.Row="0" Grid.Column="0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
VerticalContentAlignment="Center" |
||||||
|
DefaultLabelPosition="Right"> |
||||||
|
<controls1:CommandBar.PrimaryCommands> |
||||||
|
<controls1:CommandBarButton |
||||||
|
IconSource="Refresh" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Label="{x:Static lang:Resources.Action_Refresh}" |
||||||
|
Command="{Binding LoadInstalledWorkflowsCommand}" /> |
||||||
|
|
||||||
|
<controls1:CommandBarSeparator /> |
||||||
|
|
||||||
|
<controls1:CommandBarElementContainer> |
||||||
|
<StackPanel Orientation="Horizontal"> |
||||||
|
<avalonia:Icon FontSize="18" |
||||||
|
Value="fa-solid fa-info" |
||||||
|
Margin="8,0" /> |
||||||
|
<TextBlock Text="Drag & drop one of the cards below into ComfyUI to load the workflow" |
||||||
|
VerticalAlignment="Center" /> |
||||||
|
</StackPanel> |
||||||
|
</controls1:CommandBarElementContainer> |
||||||
|
</controls1:CommandBar.PrimaryCommands> |
||||||
|
</controls1:CommandBar> |
||||||
|
|
||||||
|
<ScrollViewer Grid.Column="0" |
||||||
|
Grid.Row="1"> |
||||||
|
<ItemsRepeater ItemsSource="{Binding DisplayedWorkflows}"> |
||||||
|
<ItemsRepeater.Layout> |
||||||
|
<!-- <UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4"/> --> |
||||||
|
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4" /> |
||||||
|
</ItemsRepeater.Layout> |
||||||
|
<ItemsRepeater.ItemTemplate> |
||||||
|
<DataTemplate x:DataType="{x:Type models:OpenArtMetadata}"> |
||||||
|
<Border |
||||||
|
Name="HoverBorder" |
||||||
|
Padding="0" |
||||||
|
BorderThickness="0" |
||||||
|
Margin="8" |
||||||
|
ClipToBounds="True" |
||||||
|
CornerRadius="8"> |
||||||
|
<Interaction.Behaviors> |
||||||
|
<BehaviorCollection> |
||||||
|
<controls:BetterContextDragBehavior |
||||||
|
Context="{Binding FilePath}" |
||||||
|
DataFormat="Files" |
||||||
|
HorizontalDragThreshold="6" |
||||||
|
VerticalDragThreshold="6" /> |
||||||
|
</BehaviorCollection> |
||||||
|
</Interaction.Behaviors> |
||||||
|
<Border.ContextFlyout> |
||||||
|
<MenuFlyout> |
||||||
|
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}" |
||||||
|
IsVisible="{Binding HasMetadata}" |
||||||
|
Command="{StaticResource OpenOnOpenArtCommand}" |
||||||
|
CommandParameter="{Binding }"> |
||||||
|
<MenuItem.Icon> |
||||||
|
<fluent:SymbolIcon Symbol="Open" /> |
||||||
|
</MenuItem.Icon> |
||||||
|
</MenuItem> |
||||||
|
<MenuItem Header="{x:Static lang:Resources.Action_OpenInExplorer}" |
||||||
|
Command="{StaticResource OpenInExplorerCommand}" |
||||||
|
CommandParameter="{Binding }"> |
||||||
|
<MenuItem.Icon> |
||||||
|
<fluent:SymbolIcon Symbol="Folder" /> |
||||||
|
</MenuItem.Icon> |
||||||
|
</MenuItem> |
||||||
|
<MenuItem Header="{x:Static lang:Resources.Action_Delete}" |
||||||
|
Command="{StaticResource DeleteCommand}" |
||||||
|
CommandParameter="{Binding }"> |
||||||
|
<MenuItem.Icon> |
||||||
|
<fluent:SymbolIcon Symbol="Delete" /> |
||||||
|
</MenuItem.Icon> |
||||||
|
</MenuItem> |
||||||
|
</MenuFlyout> |
||||||
|
</Border.ContextFlyout> |
||||||
|
<Button |
||||||
|
Name="ModelCard" |
||||||
|
Classes="transparent-full" |
||||||
|
Padding="0" |
||||||
|
BorderThickness="0" |
||||||
|
VerticalContentAlignment="Top" |
||||||
|
CornerRadius="8"> |
||||||
|
<Grid RowDefinitions="*, Auto"> |
||||||
|
<labs:AsyncImage |
||||||
|
Grid.Row="0" |
||||||
|
Grid.RowSpan="2" |
||||||
|
CornerRadius="8" |
||||||
|
Width="330" |
||||||
|
Height="400" |
||||||
|
Source="{Binding FirstThumbnail}" |
||||||
|
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}" |
||||||
|
Stretch="UniformToFill" /> |
||||||
|
|
||||||
|
<avalonia:Icon Grid.Row="0" |
||||||
|
Grid.RowSpan="2" |
||||||
|
CornerRadius="8" |
||||||
|
Width="330" |
||||||
|
Height="400" |
||||||
|
FontSize="100" |
||||||
|
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNull}, FallbackValue=False}" |
||||||
|
Value="fa-regular fa-file-code" /> |
||||||
|
|
||||||
|
<!-- Username pill card --> |
||||||
|
<Border |
||||||
|
BoxShadow="inset 1.2 0 80 1.8 #66000000" |
||||||
|
CornerRadius="16" |
||||||
|
Margin="4" |
||||||
|
Grid.Row="0" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
VerticalAlignment="Bottom"> |
||||||
|
<Border.Resources> |
||||||
|
<DropShadowEffect |
||||||
|
x:Key="TextDropShadowEffect" |
||||||
|
BlurRadius="12" |
||||||
|
Color="#FF000000" |
||||||
|
Opacity="0.9" /> |
||||||
|
<DropShadowEffect |
||||||
|
x:Key="ImageDropShadowEffect" |
||||||
|
BlurRadius="12" |
||||||
|
Color="#FF000000" |
||||||
|
Opacity="0.2" /> |
||||||
|
</Border.Resources> |
||||||
|
<Button |
||||||
|
Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||||
|
CommandParameter="{Binding Workflow.Creator.DevProfileUrl}" |
||||||
|
CornerRadius="16" |
||||||
|
Classes="transparent" |
||||||
|
Padding="10,4"> |
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6"> |
||||||
|
<labs:AsyncImage |
||||||
|
Width="22" |
||||||
|
Height="22" |
||||||
|
Effect="{StaticResource ImageDropShadowEffect}" |
||||||
|
CornerRadius="11" |
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality" |
||||||
|
IsVisible="{Binding Workflow.Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" |
||||||
|
Source="{Binding Workflow.Creator.Avatar}" /> |
||||||
|
<TextBlock |
||||||
|
VerticalAlignment="Center" |
||||||
|
Foreground="{DynamicResource TextControlForeground}" |
||||||
|
Effect="{StaticResource TextDropShadowEffect}" |
||||||
|
Text="{Binding Workflow.Creator.Name}" /> |
||||||
|
</StackPanel> |
||||||
|
</Button> |
||||||
|
</Border> |
||||||
|
|
||||||
|
<Border |
||||||
|
Name="ModelCardBottom" |
||||||
|
Grid.Row="1"> |
||||||
|
<TextBlock |
||||||
|
Padding="16" |
||||||
|
Margin="8,0,8,0" |
||||||
|
HorizontalAlignment="Center" |
||||||
|
VerticalAlignment="Center" |
||||||
|
FontWeight="SemiBold" |
||||||
|
Foreground="{DynamicResource TextControlForeground}" |
||||||
|
LetterSpacing="0.33" |
||||||
|
TextWrapping="Wrap" |
||||||
|
MaxWidth="315" |
||||||
|
Text="{Binding Workflow.Name}" |
||||||
|
ToolTip.Tip="{Binding Workflow.Name}" /> |
||||||
|
</Border> |
||||||
|
</Grid> |
||||||
|
</Button> |
||||||
|
</Border> |
||||||
|
</DataTemplate> |
||||||
|
</ItemsRepeater.ItemTemplate> |
||||||
|
</ItemsRepeater> |
||||||
|
</ScrollViewer> |
||||||
|
</Grid> |
||||||
|
</controls:UserControlBase> |
@ -0,0 +1,13 @@ |
|||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Views; |
||||||
|
|
||||||
|
[Singleton] |
||||||
|
public partial class InstalledWorkflowsPage : UserControlBase |
||||||
|
{ |
||||||
|
public InstalledWorkflowsPage() |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,360 @@ |
|||||||
|
<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||||
|
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" |
||||||
|
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" |
||||||
|
xmlns:openArt="clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt;assembly=StabilityMatrix.Core" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||||
|
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" |
||||||
|
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" |
||||||
|
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia" |
||||||
|
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||||
|
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls" |
||||||
|
x:DataType="viewModels:OpenArtBrowserViewModel" |
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.OpenArtBrowserPage"> |
||||||
|
<UserControl.Styles> |
||||||
|
<Style Selector="Border#HoverBorder"> |
||||||
|
<Setter Property="Transitions"> |
||||||
|
<Transitions> |
||||||
|
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237" /> |
||||||
|
</Transitions> |
||||||
|
</Setter> |
||||||
|
|
||||||
|
<Style Selector="^ labs|AsyncImage"> |
||||||
|
<Setter Property="Transitions"> |
||||||
|
<Transitions> |
||||||
|
<TransformOperationsTransition Property="RenderTransform" |
||||||
|
Duration="0:0:0.237"> |
||||||
|
<TransformOperationsTransition.Easing> |
||||||
|
<QuadraticEaseInOut /> |
||||||
|
</TransformOperationsTransition.Easing> |
||||||
|
</TransformOperationsTransition> |
||||||
|
</Transitions> |
||||||
|
</Setter> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:pointerover"> |
||||||
|
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000" /> |
||||||
|
<Setter Property="Cursor" Value="Hand" /> |
||||||
|
<Style Selector="^ labs|AsyncImage"> |
||||||
|
<Setter Property="CornerRadius" Value="12" /> |
||||||
|
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^ Border#ModelCardBottom"> |
||||||
|
<Setter Property="Background" Value="#CC000000" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
|
||||||
|
<Style Selector="^:not(:pointerover)"> |
||||||
|
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000" /> |
||||||
|
<Setter Property="Cursor" Value="Arrow" /> |
||||||
|
<Style Selector="^ labs|AsyncImage"> |
||||||
|
<Setter Property="CornerRadius" Value="8" /> |
||||||
|
<Setter Property="RenderTransform" Value="scale(1, 1)" /> |
||||||
|
</Style> |
||||||
|
<Style Selector="^ Border#ModelCardBottom"> |
||||||
|
<Setter Property="Background" Value="#99000000" /> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</Style> |
||||||
|
</UserControl.Styles> |
||||||
|
|
||||||
|
<UserControl.Resources> |
||||||
|
<input:StandardUICommand |
||||||
|
x:Key="OpenModelCommand" |
||||||
|
Command="{Binding OpenModelCommand}" /> |
||||||
|
|
||||||
|
<input:StandardUICommand |
||||||
|
x:Key="OpenOnOpenArtCommand" |
||||||
|
Command="{Binding OpenOnOpenArtCommand}" /> |
||||||
|
|
||||||
|
<input:StandardUICommand |
||||||
|
x:Key="OpenWorkflowCommand" |
||||||
|
Command="{Binding OpenWorkflowCommand}" /> |
||||||
|
|
||||||
|
<converters:KiloFormatterStringConverter x:Key="KiloFormatterConverter" /> |
||||||
|
<DataTemplate x:Key="OpenArtWorkflowTemplate" DataType="{x:Type openArt:OpenArtSearchResult}"> |
||||||
|
<Border |
||||||
|
Name="HoverBorder" |
||||||
|
Padding="0" |
||||||
|
BorderThickness="0" |
||||||
|
Margin="8" |
||||||
|
ClipToBounds="True" |
||||||
|
CornerRadius="8"> |
||||||
|
<Border.ContextFlyout> |
||||||
|
<MenuFlyout> |
||||||
|
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}" |
||||||
|
Command="{StaticResource OpenOnOpenArtCommand}" |
||||||
|
CommandParameter="{Binding }"> |
||||||
|
<MenuItem.Icon> |
||||||
|
<ui:SymbolIcon Symbol="Open" /> |
||||||
|
</MenuItem.Icon> |
||||||
|
</MenuItem> |
||||||
|
</MenuFlyout> |
||||||
|
</Border.ContextFlyout> |
||||||
|
<Button |
||||||
|
Name="ModelCard" |
||||||
|
Classes="transparent-full" |
||||||
|
Padding="0" |
||||||
|
BorderThickness="0" |
||||||
|
VerticalContentAlignment="Top" |
||||||
|
CornerRadius="8" |
||||||
|
Command="{StaticResource OpenWorkflowCommand}" |
||||||
|
CommandParameter="{Binding }"> |
||||||
|
<Grid RowDefinitions="*, Auto"> |
||||||
|
<labs:AsyncImage |
||||||
|
Grid.Row="0" |
||||||
|
Grid.RowSpan="2" |
||||||
|
CornerRadius="8" |
||||||
|
Width="330" |
||||||
|
Height="400" |
||||||
|
Source="{Binding Thumbnails[0].Url}" |
||||||
|
Stretch="UniformToFill" /> |
||||||
|
|
||||||
|
<!-- Username pill card --> |
||||||
|
<Border |
||||||
|
BoxShadow="inset 1.2 0 80 1.8 #66000000" |
||||||
|
CornerRadius="16" |
||||||
|
Margin="4" |
||||||
|
Grid.Row="0" |
||||||
|
ClipToBounds="True" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
VerticalAlignment="Bottom"> |
||||||
|
<Border.Resources> |
||||||
|
<DropShadowEffect |
||||||
|
x:Key="TextDropShadowEffect" |
||||||
|
BlurRadius="12" |
||||||
|
Color="#FF000000" |
||||||
|
Opacity="0.9" /> |
||||||
|
<DropShadowEffect |
||||||
|
x:Key="ImageDropShadowEffect" |
||||||
|
BlurRadius="12" |
||||||
|
Color="#FF000000" |
||||||
|
Opacity="0.2" /> |
||||||
|
</Border.Resources> |
||||||
|
<Button |
||||||
|
Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||||
|
CommandParameter="{Binding Creator.DevProfileUrl}" |
||||||
|
CornerRadius="16" |
||||||
|
Classes="transparent" |
||||||
|
Padding="10,4"> |
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6"> |
||||||
|
<labs:AsyncImage |
||||||
|
Width="22" |
||||||
|
Height="22" |
||||||
|
ClipToBounds="True" |
||||||
|
Effect="{StaticResource ImageDropShadowEffect}" |
||||||
|
CornerRadius="11" |
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality" |
||||||
|
IsVisible="{Binding Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" |
||||||
|
Source="{Binding Creator.Avatar}"> |
||||||
|
</labs:AsyncImage> |
||||||
|
<TextBlock |
||||||
|
VerticalAlignment="Center" |
||||||
|
Foreground="{DynamicResource TextControlForeground}" |
||||||
|
Effect="{StaticResource TextDropShadowEffect}" |
||||||
|
Text="{Binding Creator.Name}" /> |
||||||
|
</StackPanel> |
||||||
|
</Button> |
||||||
|
</Border> |
||||||
|
|
||||||
|
<Border |
||||||
|
Name="ModelCardBottom" |
||||||
|
Grid.Row="1"> |
||||||
|
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto, Auto, Auto"> |
||||||
|
|
||||||
|
<!-- |
||||||
|
TextTrimming causing issues with unicode chars until |
||||||
|
https://github.com/AvaloniaUI/Avalonia/pull/13385 is released |
||||||
|
--> |
||||||
|
<TextBlock |
||||||
|
Grid.ColumnSpan="2" |
||||||
|
MaxWidth="250" |
||||||
|
Margin="8,0,8,0" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
VerticalAlignment="Center" |
||||||
|
FontWeight="SemiBold" |
||||||
|
Foreground="{DynamicResource TextControlForeground}" |
||||||
|
LetterSpacing="0.33" |
||||||
|
Text="{Binding Name}" |
||||||
|
TextWrapping="NoWrap" |
||||||
|
ToolTip.Tip="{Binding Name}" /> |
||||||
|
|
||||||
|
<StackPanel |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="0" |
||||||
|
Orientation="Horizontal"> |
||||||
|
|
||||||
|
<controls:StarsRating |
||||||
|
Margin="8,8,0,8" |
||||||
|
Background="#66000000" |
||||||
|
FontSize="16" |
||||||
|
Foreground="{DynamicResource ThemeEldenRingOrangeColor}" |
||||||
|
Value="{Binding Stats.Rating}" /> |
||||||
|
<TextBlock |
||||||
|
Margin="4,0,0,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{Binding Stats.NumReviews}" |
||||||
|
TextAlignment="Center" /> |
||||||
|
</StackPanel> |
||||||
|
|
||||||
|
<StackPanel |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="1" |
||||||
|
HorizontalAlignment="Right" |
||||||
|
Orientation="Horizontal"> |
||||||
|
<avalonia:Icon Value="fa-solid fa-heart" /> |
||||||
|
<TextBlock |
||||||
|
Margin="4,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{Binding Stats.NumLikes, Converter={StaticResource KiloFormatterConverter}}" /> |
||||||
|
|
||||||
|
<avalonia:Icon Margin="4,0" Value="fa-solid fa-download" /> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,4,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{Binding Stats.NumDownloads, Converter={StaticResource KiloFormatterConverter}}" /> |
||||||
|
</StackPanel> |
||||||
|
<Button |
||||||
|
Grid.Row="0" |
||||||
|
Grid.Column="1" |
||||||
|
Width="32" |
||||||
|
Margin="0,4,4,0" |
||||||
|
HorizontalAlignment="Right" |
||||||
|
VerticalAlignment="Top" |
||||||
|
HorizontalContentAlignment="Right" |
||||||
|
VerticalContentAlignment="Top" |
||||||
|
BorderThickness="0" |
||||||
|
Classes="transparent"> |
||||||
|
<ui:SymbolIcon FontSize="18" Symbol="MoreVertical" /> |
||||||
|
<Button.Flyout> |
||||||
|
<MenuFlyout> |
||||||
|
<MenuItem Command="{StaticResource OpenModelCommand}" |
||||||
|
CommandParameter="{Binding }" |
||||||
|
Header="{x:Static lang:Resources.Action_OpenOnOpenArt}"> |
||||||
|
<MenuItem.Icon> |
||||||
|
<ui:SymbolIcon Symbol="Open" /> |
||||||
|
</MenuItem.Icon> |
||||||
|
</MenuItem> |
||||||
|
</MenuFlyout> |
||||||
|
</Button.Flyout> |
||||||
|
</Button> |
||||||
|
</Grid> |
||||||
|
</Border> |
||||||
|
</Grid> |
||||||
|
</Button> |
||||||
|
</Border> |
||||||
|
|
||||||
|
</DataTemplate> |
||||||
|
</UserControl.Resources> |
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto, Auto, *, Auto"> |
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" |
||||||
|
Margin="8"> |
||||||
|
<TextBox |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
Text="{Binding SearchQuery, Mode=TwoWay}" |
||||||
|
Watermark="{x:Static lang:Resources.Action_Search}" |
||||||
|
Classes="search"/> |
||||||
|
|
||||||
|
<Button |
||||||
|
Grid.Column="1" |
||||||
|
Width="80" |
||||||
|
Margin="8,0,8,0" |
||||||
|
VerticalAlignment="Stretch" |
||||||
|
Classes="accent" |
||||||
|
Command="{Binding SearchButtonCommand}" |
||||||
|
IsDefault="True"> |
||||||
|
<Grid> |
||||||
|
<controls:ProgressRing |
||||||
|
MinWidth="16" |
||||||
|
MinHeight="16" |
||||||
|
VerticalAlignment="Center" |
||||||
|
BorderThickness="4" |
||||||
|
IsIndeterminate="True" |
||||||
|
IsVisible="{Binding SearchButtonCommand.IsRunning}" /> |
||||||
|
<TextBlock |
||||||
|
VerticalAlignment="Center" |
||||||
|
IsVisible="{Binding !SearchButtonCommand.IsRunning}" |
||||||
|
Text="{x:Static lang:Resources.Action_Search}" /> |
||||||
|
</Grid> |
||||||
|
</Button> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" |
||||||
|
Margin="8,0,0,8" |
||||||
|
Orientation="Vertical" |
||||||
|
IsVisible="{Binding SearchQuery, Converter={x:Static StringConverters.IsNullOrEmpty}}"> |
||||||
|
<Label Content="{x:Static lang:Resources.Label_Sort}" /> |
||||||
|
<ComboBox |
||||||
|
MinWidth="100" |
||||||
|
ItemsSource="{Binding AllSortModes}" |
||||||
|
SelectedItem="{Binding SelectedSortMode}"/> |
||||||
|
</StackPanel> |
||||||
|
|
||||||
|
<controls:ProgressRing Grid.Row="2" |
||||||
|
IsVisible="{Binding IsLoading}" |
||||||
|
IsIndeterminate="True" |
||||||
|
Width="128" |
||||||
|
Height="128"/> |
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="2" |
||||||
|
ScrollChanged="ScrollViewer_OnScrollChanged" |
||||||
|
IsVisible="{Binding !IsLoading}"> |
||||||
|
<ItemsRepeater ItemsSource="{Binding SearchResults}" |
||||||
|
ItemTemplate="{StaticResource OpenArtWorkflowTemplate}"> |
||||||
|
<ItemsRepeater.Layout> |
||||||
|
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4"/> |
||||||
|
</ItemsRepeater.Layout> |
||||||
|
</ItemsRepeater> |
||||||
|
</ScrollViewer> |
||||||
|
|
||||||
|
<StackPanel Grid.Row="3" |
||||||
|
HorizontalAlignment="Center" |
||||||
|
Margin="0,8,0,8" |
||||||
|
Orientation="Horizontal"> |
||||||
|
<Button |
||||||
|
Margin="0,0,8,0" |
||||||
|
Command="{Binding FirstPageCommand}" |
||||||
|
IsEnabled="{Binding CanGoBack}" |
||||||
|
ToolTip.Tip="{x:Static lang:Resources.Label_FirstPage}"> |
||||||
|
<avalonia:Icon Value="fa-solid fa-backward-fast" /> |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
Margin="0,0,16,0" |
||||||
|
Command="{Binding PreviousPageCommand}" |
||||||
|
IsEnabled="{Binding CanGoBack}" |
||||||
|
ToolTip.Tip="{x:Static lang:Resources.Label_PreviousPage}"> |
||||||
|
<avalonia:Icon Value="fa-solid fa-caret-left" /> |
||||||
|
</Button> |
||||||
|
<TextBlock Margin="8,0,4,0" TextAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_Page}" |
||||||
|
VerticalAlignment="Center"/> |
||||||
|
<ui:NumberBox Value="{Binding DisplayedPageNumber, FallbackValue=1}" |
||||||
|
VerticalAlignment="Center" |
||||||
|
SpinButtonPlacementMode="Hidden" |
||||||
|
TextAlignment="Center"/> |
||||||
|
<TextBlock Margin="4,0,8,0" VerticalAlignment="Center"> |
||||||
|
<Run Text="/"/> |
||||||
|
<Run Text="{Binding PageCount, FallbackValue=5}"/> |
||||||
|
</TextBlock> |
||||||
|
<Button |
||||||
|
Margin="16,0,8,0" |
||||||
|
Command="{Binding NextPageCommand}" |
||||||
|
IsEnabled="{Binding CanGoForward}" |
||||||
|
ToolTip.Tip="{x:Static lang:Resources.Label_NextPage}"> |
||||||
|
<avalonia:Icon Value="fa-solid fa-caret-right" /> |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
Command="{Binding LastPageCommand}" |
||||||
|
IsEnabled="{Binding CanGoToEnd}" |
||||||
|
ToolTip.Tip="{x:Static lang:Resources.Label_LastPage}"> |
||||||
|
<avalonia:Icon Value="fa-solid fa-forward-fast" /> |
||||||
|
</Button> |
||||||
|
</StackPanel> |
||||||
|
</Grid> |
||||||
|
</controls:UserControlBase> |
@ -0,0 +1,41 @@ |
|||||||
|
using System; |
||||||
|
using AsyncAwaitBestPractices; |
||||||
|
using Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Views; |
||||||
|
|
||||||
|
[Singleton] |
||||||
|
public partial class OpenArtBrowserPage : UserControlBase |
||||||
|
{ |
||||||
|
private readonly ISettingsManager settingsManager; |
||||||
|
|
||||||
|
public OpenArtBrowserPage(ISettingsManager settingsManager) |
||||||
|
{ |
||||||
|
this.settingsManager = settingsManager; |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
|
||||||
|
private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) |
||||||
|
{ |
||||||
|
if (sender is not ScrollViewer scrollViewer) |
||||||
|
return; |
||||||
|
|
||||||
|
if (scrollViewer.Offset.Y == 0) |
||||||
|
return; |
||||||
|
|
||||||
|
var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f; |
||||||
|
|
||||||
|
if ( |
||||||
|
isAtEnd |
||||||
|
&& settingsManager.Settings.IsWorkflowInfiniteScrollEnabled |
||||||
|
&& DataContext is IInfinitelyScroll scroll |
||||||
|
) |
||||||
|
{ |
||||||
|
scroll.LoadNextPageAsync().SafeFireAndForget(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
<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:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||||
|
x:DataType="viewModels:WorkflowsPageViewModel" |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.WorkflowsPage"> |
||||||
|
<TabControl ItemsSource="{Binding Pages}" |
||||||
|
SelectedItem="{Binding SelectedPage}"/> |
||||||
|
</controls:UserControlBase> |
@ -0,0 +1,13 @@ |
|||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Views; |
||||||
|
|
||||||
|
[Singleton] |
||||||
|
public partial class WorkflowsPage : UserControlBase |
||||||
|
{ |
||||||
|
public WorkflowsPage() |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
using Refit; |
||||||
|
using StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Api; |
||||||
|
|
||||||
|
[Headers("User-Agent: StabilityMatrix")] |
||||||
|
public interface IOpenArtApi |
||||||
|
{ |
||||||
|
[Get("/feed")] |
||||||
|
Task<OpenArtSearchResponse> GetFeedAsync([Query] OpenArtFeedRequest request); |
||||||
|
|
||||||
|
[Get("/list")] |
||||||
|
Task<OpenArtSearchResponse> SearchAsync([Query] OpenArtSearchRequest request); |
||||||
|
|
||||||
|
[Post("/download")] |
||||||
|
Task<OpenArtDownloadResponse> DownloadWorkflowAsync([Body] OpenArtDownloadRequest request); |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class NodesCount |
||||||
|
{ |
||||||
|
[JsonPropertyName("total")] |
||||||
|
public long Total { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("primitive")] |
||||||
|
public long Primitive { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("custom")] |
||||||
|
public long Custom { get; set; } |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtCreator |
||||||
|
{ |
||||||
|
[JsonPropertyName("uid")] |
||||||
|
public string Uid { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("name")] |
||||||
|
public string Name { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("bio")] |
||||||
|
public string Bio { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("avatar")] |
||||||
|
public Uri Avatar { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("username")] |
||||||
|
public string Username { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("dev_profile_url")] |
||||||
|
public string DevProfileUrl { get; set; } |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtDateTime |
||||||
|
{ |
||||||
|
[JsonPropertyName("_seconds")] |
||||||
|
public long Seconds { get; set; } |
||||||
|
|
||||||
|
public DateTimeOffset ToDateTimeOffset() |
||||||
|
{ |
||||||
|
return DateTimeOffset.FromUnixTimeSeconds(Seconds); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
using Refit; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtDownloadRequest |
||||||
|
{ |
||||||
|
[AliasAs("workflow_id")] |
||||||
|
[JsonPropertyName("workflow_id")] |
||||||
|
public required string WorkflowId { get; set; } |
||||||
|
|
||||||
|
[AliasAs("version_tag")] |
||||||
|
[JsonPropertyName("version_tag")] |
||||||
|
public string VersionTag { get; set; } = "latest"; |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtDownloadResponse |
||||||
|
{ |
||||||
|
[JsonPropertyName("filename")] |
||||||
|
public string Filename { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("payload")] |
||||||
|
public string Payload { get; set; } |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
using Refit; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Note that parameters Category, Custom Node and Sort should be used separately |
||||||
|
/// </summary> |
||||||
|
public class OpenArtFeedRequest |
||||||
|
{ |
||||||
|
[AliasAs("category")] |
||||||
|
public string Category { get; set; } |
||||||
|
|
||||||
|
[AliasAs("sort")] |
||||||
|
public string Sort { get; set; } |
||||||
|
|
||||||
|
[AliasAs("custom_node")] |
||||||
|
public string CustomNode { get; set; } |
||||||
|
|
||||||
|
[AliasAs("cursor")] |
||||||
|
public string Cursor { get; set; } |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
using Refit; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtSearchRequest |
||||||
|
{ |
||||||
|
[AliasAs("keyword")] |
||||||
|
public required string Keyword { get; set; } |
||||||
|
|
||||||
|
[AliasAs("pageSize")] |
||||||
|
public int PageSize { get; set; } = 30; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 0-based index of the page to retrieve |
||||||
|
/// </summary> |
||||||
|
[AliasAs("currentPage")] |
||||||
|
public int CurrentPage { get; set; } = 0; |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtSearchResponse |
||||||
|
{ |
||||||
|
[JsonPropertyName("items")] |
||||||
|
public IEnumerable<OpenArtSearchResult> Items { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("total")] |
||||||
|
public int Total { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("nextCursor")] |
||||||
|
public string? NextCursor { get; set; } |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtSearchResult |
||||||
|
{ |
||||||
|
[JsonPropertyName("id")] |
||||||
|
public string Id { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("creator")] |
||||||
|
public OpenArtCreator Creator { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("stats")] |
||||||
|
public OpenArtStats Stats { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("nodes_index")] |
||||||
|
public IEnumerable<string> NodesIndex { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("name")] |
||||||
|
public string Name { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("description")] |
||||||
|
public string Description { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("categories")] |
||||||
|
public IEnumerable<string> Categories { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("thumbnails")] |
||||||
|
public List<OpenArtThumbnail> Thumbnails { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("nodes_count")] |
||||||
|
public NodesCount NodesCount { get; set; } |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtStats |
||||||
|
{ |
||||||
|
[JsonPropertyName("num_shares")] |
||||||
|
public int NumShares { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("num_bookmarks")] |
||||||
|
public int NumBookmarks { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("num_reviews")] |
||||||
|
public int NumReviews { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("rating")] |
||||||
|
public double Rating { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("num_comments")] |
||||||
|
public int NumComments { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("num_likes")] |
||||||
|
public int NumLikes { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("num_downloads")] |
||||||
|
public int NumDownloads { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("num_runs")] |
||||||
|
public int NumRuns { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("num_views")] |
||||||
|
public int NumViews { get; set; } |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
|
||||||
|
public class OpenArtThumbnail |
||||||
|
{ |
||||||
|
[JsonPropertyName("width")] |
||||||
|
public int Width { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("url")] |
||||||
|
public Uri Url { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("height")] |
||||||
|
public int Height { get; set; } |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@ |
|||||||
|
using System.Text.Json; |
||||||
|
using System.Text.Json.Nodes; |
||||||
|
using StabilityMatrix.Core.Api; |
||||||
|
using StabilityMatrix.Core.Models.Api.OpenArt; |
||||||
|
using StabilityMatrix.Core.Models.Progress; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.PackageModification; |
||||||
|
|
||||||
|
public class DownloadOpenArtWorkflowStep( |
||||||
|
IOpenArtApi openArtApi, |
||||||
|
OpenArtSearchResult workflow, |
||||||
|
ISettingsManager settingsManager |
||||||
|
) : IPackageStep |
||||||
|
{ |
||||||
|
public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null) |
||||||
|
{ |
||||||
|
var workflowData = await openArtApi |
||||||
|
.DownloadWorkflowAsync(new OpenArtDownloadRequest { WorkflowId = workflow.Id }) |
||||||
|
.ConfigureAwait(false); |
||||||
|
|
||||||
|
var workflowJson = JsonSerializer.SerializeToNode(workflow); |
||||||
|
|
||||||
|
Directory.CreateDirectory(settingsManager.WorkflowDirectory); |
||||||
|
var filePath = Path.Combine(settingsManager.WorkflowDirectory, $"{workflowData.Filename}.json"); |
||||||
|
|
||||||
|
var jsonObject = JsonNode.Parse(workflowData.Payload) as JsonObject; |
||||||
|
jsonObject?.Add("sm_workflow_data", workflowJson); |
||||||
|
|
||||||
|
await File.WriteAllTextAsync(filePath, JsonSerializer.Serialize(jsonObject)).ConfigureAwait(false); |
||||||
|
|
||||||
|
progress?.Report(new ProgressReport(1f, "Downloaded OpenArt Workflow")); |
||||||
|
} |
||||||
|
|
||||||
|
public string ProgressTitle => "Downloading OpenArt Workflow"; |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
using StabilityMatrix.Core.Models.Packages; |
||||||
|
using StabilityMatrix.Core.Models.Progress; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Models.PackageModification; |
||||||
|
|
||||||
|
public class SetupOutputSharingStep(BasePackage package, string installPath) : IPackageStep |
||||||
|
{ |
||||||
|
public Task ExecuteAsync(IProgress<ProgressReport>? progress = null) |
||||||
|
{ |
||||||
|
package.SetupOutputFolderLinks(installPath); |
||||||
|
return Task.CompletedTask; |
||||||
|
} |
||||||
|
|
||||||
|
public string ProgressTitle => "Setting up output sharing..."; |
||||||
|
} |
Loading…
Reference in new issue