diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
index 4a11a99e..3fc6455b 100644
--- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs
+++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs
@@ -932,12 +932,20 @@ The gallery images are often inpainted, but you will get something very similar
{
new()
{
- Name = "Test Workflow",
- Creator = "Test Creator",
- ThumbnailUrls =
- [
- "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512"
- ]
+ Workflow = new()
+ {
+ Name = "Test Workflow",
+ Creator = new OpenArtCreator { Name = "Test Creator" },
+ Thumbnails =
+ [
+ new OpenArtThumbnail
+ {
+ Url = new Uri(
+ "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512"
+ )
+ }
+ ]
+ }
}
};
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
index 5dd40cc8..388037ce 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
+++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
@@ -1319,6 +1319,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Error retrieving workflows.
+ ///
+ public static string Label_ErrorRetrievingWorkflows {
+ get {
+ return ResourceManager.GetString("Label_ErrorRetrievingWorkflows", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Everything looks good!.
///
@@ -1355,6 +1364,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Finished importing workflow and custom nodes.
+ ///
+ public static string Label_FinishedImportingWorkflow {
+ get {
+ return ResourceManager.GetString("Label_FinishedImportingWorkflow", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to First Page.
///
@@ -2606,6 +2624,24 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Workflow Deleted.
+ ///
+ public static string Label_WorkflowDeleted {
+ get {
+ return ResourceManager.GetString("Label_WorkflowDeleted", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} deleted successfully.
+ ///
+ public static string Label_WorkflowDeletedSuccessfully {
+ get {
+ return ResourceManager.GetString("Label_WorkflowDeletedSuccessfully", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Workflow Description.
///
@@ -2615,6 +2651,24 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to The workflow and custom nodes have been imported..
+ ///
+ public static string Label_WorkflowImportComplete {
+ get {
+ return ResourceManager.GetString("Label_WorkflowImportComplete", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Workflow Imported.
+ ///
+ public static string Label_WorkflowImported {
+ get {
+ return ResourceManager.GetString("Label_WorkflowImported", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Workflows.
///
@@ -2705,6 +2759,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Installed Workflows.
+ ///
+ public static string TabLabel_InstalledWorkflows {
+ get {
+ return ResourceManager.GetString("TabLabel_InstalledWorkflows", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Add a package to get started!.
///
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx
index 368e2867..4c70a20c 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.resx
@@ -1047,4 +1047,25 @@
Stability Matrix is already running
+
+ {0} deleted successfully
+
+
+ Workflow Deleted
+
+
+ Error retrieving workflows
+
+
+ Installed Workflows
+
+
+ Workflow Imported
+
+
+ Finished importing workflow and custom nodes
+
+
+ The workflow and custom nodes have been imported.
+
diff --git a/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs b/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
index 72d09658..7620816c 100644
--- a/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
+++ b/StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
@@ -2,27 +2,21 @@
using System.Linq;
using System.Text.Json.Serialization;
using Avalonia.Platform.Storage;
-using StabilityMatrix.Avalonia;
+using StabilityMatrix.Core.Models.Api.OpenArt;
-namespace StabilityMatrix.Core.Models.Api.OpenArt;
+namespace StabilityMatrix.Avalonia.Models;
public class OpenArtMetadata
{
- [JsonPropertyName("workflow_id")]
- public string? Id { get; set; }
-
- [JsonPropertyName("workflow_name")]
- public string? Name { get; set; }
-
- [JsonPropertyName("creator")]
- public string? Creator { get; set; }
-
- [JsonPropertyName("thumbnails")]
- public IEnumerable? ThumbnailUrls { get; set; }
+ [JsonPropertyName("sm_workflow_data")]
+ public OpenArtSearchResult? Workflow { get; set; }
[JsonIgnore]
- public string? FirstThumbnail => ThumbnailUrls?.FirstOrDefault();
+ public string? FirstThumbnail => Workflow?.Thumbnails?.Select(x => x.Url).FirstOrDefault()?.ToString();
[JsonIgnore]
public List? FilePath { get; set; }
+
+ [JsonIgnore]
+ public bool HasMetadata => Workflow?.Creator != null;
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
index 8a4c0e87..b765c797 100644
--- a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
@@ -1,28 +1,41 @@
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) : TabViewModelBase
+public partial class InstalledWorkflowsViewModel(
+ ISettingsManager settingsManager,
+ INotificationService notificationService
+) : TabViewModelBase, IDisposable
{
- public override string Header => "Installed Workflows";
+ public override string Header => Resources.TabLabel_InstalledWorkflows;
- private readonly SourceCache workflowsCache = new(x => x.Id);
+ private readonly SourceCache workflowsCache =
+ new(x => x.Workflow?.Id ?? Guid.NewGuid().ToString());
[ObservableProperty]
private IObservableCollection displayedWorkflows =
@@ -38,6 +51,7 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
return;
await LoadInstalledWorkflowsAsync();
+ EventManager.Instance.WorkflowInstalled += OnWorkflowInstalled;
}
[RelayCommand]
@@ -58,12 +72,15 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
var json = await File.ReadAllTextAsync(workflowPath);
var metadata = JsonSerializer.Deserialize(json);
- if (metadata?.Id == null)
+ if (metadata?.Workflow == null)
{
metadata = new OpenArtMetadata
{
- Id = Guid.NewGuid().ToString(),
- Name = Path.GetFileNameWithoutExtension(workflowPath)
+ Workflow = new OpenArtSearchResult
+ {
+ Id = Guid.NewGuid().ToString(),
+ Name = Path.GetFileNameWithoutExtension(workflowPath)
+ }
};
}
@@ -76,4 +93,76 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
}
}
}
+
+ [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;
+ }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
index b1dbd39a..1b7ef2f1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
@@ -12,6 +12,7 @@ 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;
@@ -182,10 +183,7 @@ public partial class OpenArtBrowserViewModel(
if (existingComfy == null || comfyPair == null)
{
- notificationService.Show(
- Resources.Label_ComfyRequiredTitle,
- "ComfyUI is required to import workflows from OpenArt"
- );
+ notificationService.Show(Resources.Label_ComfyRequiredTitle, Resources.Label_ComfyRequiredDetail);
return;
}
@@ -210,18 +208,29 @@ public partial class OpenArtBrowserViewModel(
var runner = new PackageModificationRunner
{
ShowDialogOnStart = true,
- ModificationCompleteTitle = "Workflow Imported",
- ModificationCompleteMessage = "Finished importing workflow and custom nodes"
+ ModificationCompleteTitle = Resources.Label_WorkflowImported,
+ ModificationCompleteMessage = Resources.Label_FinishedImportingWorkflow
};
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);
notificationService.Show(
- "Workflow Imported",
- "The workflow and custom nodes have been imported.",
+ 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)
@@ -262,7 +271,7 @@ public partial class OpenArtBrowserViewModel(
}
catch (ApiException e)
{
- notificationService.Show("Error retrieving workflows", e.Message);
+ notificationService.Show(Resources.Label_ErrorRetrievingWorkflows, e.Message);
}
finally
{
diff --git a/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
index 5c4d3b4b..7e569068 100644
--- a/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
@@ -10,6 +10,11 @@
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"
@@ -22,7 +27,7 @@
-
+
+
+
+
+
+
-
+
-
+
+ Margin="8,0" />
+ VerticalAlignment="Center" />
@@ -96,7 +113,7 @@
-
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
index 2d377dd1..867d2a62 100644
--- a/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
@@ -12,6 +12,7 @@
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">
@@ -23,7 +24,7 @@
-