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 workflowsCache = new(x => x.Workflow?.Id ?? Guid.NewGuid().ToString()); [ObservableProperty] private IObservableCollection displayedWorkflows = new ObservableCollectionExtended(); 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(); foreach ( var workflowPath in Directory.EnumerateFiles( settingsManager.WorkflowDirectory, "*.json", SearchOption.AllDirectories ) ) { try { var json = await File.ReadAllTextAsync(workflowPath); var metadata = JsonSerializer.Deserialize(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; } }