From bb843fa8120c28b60c2d6b8065eb14c0b29eaf64 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 10 Dec 2025 13:27:24 +0100 Subject: [PATCH] Inject PandocAvailabilityService and refactor its usage --- .../Components/ChatComponent.razor | 7 +- .../Components/ChatComponent.razor.cs | 11 ++- .../Components/ReadFileContent.razor.cs | 31 ++----- app/MindWork AI Studio/Program.cs | 1 + .../Services/PandocAvailabilityService.cs | 85 +++++++++++++++++++ 5 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/Services/PandocAvailabilityService.cs diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor index 983f1f45..6a8e4ad9 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor +++ b/app/MindWork AI Studio/Components/ChatComponent.razor @@ -82,8 +82,11 @@ } - - + + @if (this.isPandocAvailable) + { + + } @if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) { diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index b85229aa..502e6966 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -3,6 +3,7 @@ using AIStudio.Dialogs; using AIStudio.Provider; using AIStudio.Settings; using AIStudio.Settings.DataModel; +using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; @@ -33,9 +34,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable [Inject] private ILogger Logger { get; set; } = null!; - + [Inject] private IDialogService DialogService { get; init; } = null!; + + [Inject] + private PandocAvailabilityService PandocAvailabilityService { get; init; } = null!; private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top; private static readonly Dictionary USER_INPUT_ATTRIBUTES = new(); @@ -58,6 +62,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private Guid currentWorkspaceId = Guid.Empty; private CancellationTokenSource? cancellationTokenSource; private HashSet chatDocumentPaths = []; + private bool isPandocAvailable; // Unfortunately, we need the input field reference to blur the focus away. Without // this, we cannot clear the input field. @@ -198,6 +203,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable // Select the correct provider: await this.SelectProviderWhenLoadingChat(); + + // Check if Pandoc is available (no dialog or messages): + this.isPandocAvailable = await this.PandocAvailabilityService.IsAvailableAsync(); + await base.OnInitializedAsync(); } diff --git a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs index c4019fa2..150acd09 100644 --- a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs +++ b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs @@ -1,11 +1,8 @@ -using AIStudio.Dialogs; using AIStudio.Tools.Rust; using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; -using DialogOptions = AIStudio.Dialogs.DialogOptions; - namespace AIStudio.Components; public partial class ReadFileContent : MSGComponentBase @@ -21,12 +18,15 @@ public partial class ReadFileContent : MSGComponentBase [Inject] private RustService RustService { get; init; } = null!; - + [Inject] private IDialogService DialogService { get; init; } = null!; - + [Inject] private ILogger Logger { get; init; } = null!; + + [Inject] + private PandocAvailabilityService PandocAvailabilityService { get; init; } = null!; private async Task SelectFile() { @@ -57,24 +57,9 @@ public partial class ReadFileContent : MSGComponentBase } // Ensure that Pandoc is installed and ready: - var pandocState = await Pandoc.CheckAvailabilityAsync(this.RustService, showSuccessMessage: false); - if (!pandocState.IsAvailable) - { - var dialogParameters = new DialogParameters - { - { x => x.ShowInitialResultInSnackbar, false }, - }; - - var dialogReference = await this.DialogService.ShowAsync(T("Pandoc Installation"), dialogParameters, DialogOptions.FULLSCREEN); - await dialogReference.Result; - - pandocState = await Pandoc.CheckAvailabilityAsync(this.RustService, showSuccessMessage: true); - if (!pandocState.IsAvailable) - { - this.Logger.LogError("Pandoc is not available after installation attempt."); - await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, T("Pandoc may be required for importing files."))); - } - } + await this.PandocAvailabilityService.EnsureAvailabilityAsync( + showSuccessMessage: false, + showDialog: true); var fileContent = await this.RustService.ReadArbitraryFileData(selectedFile.SelectedFilePath, int.MaxValue); await this.FileContentChanged.InvokeAsync(fileContent); diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index 2514a67f..b5954efc 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -126,6 +126,7 @@ internal sealed class Program builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddScoped(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); diff --git a/app/MindWork AI Studio/Tools/Services/PandocAvailabilityService.cs b/app/MindWork AI Studio/Tools/Services/PandocAvailabilityService.cs new file mode 100644 index 00000000..14a26908 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Services/PandocAvailabilityService.cs @@ -0,0 +1,85 @@ +using AIStudio.Dialogs; +using AIStudio.Tools.PluginSystem; + +using DialogOptions = AIStudio.Dialogs.DialogOptions; + +namespace AIStudio.Tools.Services; + +/// +/// Service to check Pandoc availability and ensure installation. +/// This service encapsulates the logic for checking if Pandoc is installed +/// and showing the installation dialog if needed. +/// +public sealed class PandocAvailabilityService(RustService rustService, IDialogService dialogService, ILogger logger) +{ + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PandocAvailabilityService).Namespace, nameof(PandocAvailabilityService)); + + private RustService RustService => rustService; + + private IDialogService DialogService => dialogService; + + private ILogger Logger => logger; + + private PandocInstallation? cachedInstallation; + + /// + /// Checks if Pandoc is available and shows the installation dialog if needed. + /// + /// Whether to show a success message if Pandoc is available. + /// Whether to show the installation dialog if Pandoc is not available. + /// The Pandoc installation state. + public async Task EnsureAvailabilityAsync(bool showSuccessMessage = false, bool showDialog = true) + { + // Check if Pandoc is available: + var pandocState = await Pandoc.CheckAvailabilityAsync(this.RustService, showMessages: false, showSuccessMessage: showSuccessMessage); + + // Cache the result: + this.cachedInstallation = pandocState; + + // If not available, show installation dialog: + if (!pandocState.IsAvailable && showDialog) + { + var dialogParameters = new DialogParameters + { + { x => x.ShowInitialResultInSnackbar, false }, + }; + + var dialogReference = await this.DialogService.ShowAsync(TB("Pandoc Installation"), dialogParameters, DialogOptions.FULLSCREEN); + await dialogReference.Result; + + // Re-check availability after dialog: + pandocState = await Pandoc.CheckAvailabilityAsync(this.RustService, showMessages: showSuccessMessage, showSuccessMessage: showSuccessMessage); + this.cachedInstallation = pandocState; + + if (!pandocState.IsAvailable) + { + this.Logger.LogError("Pandoc is not available after installation attempt."); + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Pandoc may be required for importing files."))); + } + } + + return pandocState; + } + + /// + /// Checks if Pandoc is available without showing any dialogs or messages. + /// Uses cached result if available to avoid redundant checks. + /// + /// True if Pandoc is available, false otherwise. + public async Task IsAvailableAsync() + { + if (this.cachedInstallation.HasValue) + return this.cachedInstallation.Value.IsAvailable; + + var pandocState = await Pandoc.CheckAvailabilityAsync(this.RustService, showMessages: false, showSuccessMessage: false); + this.cachedInstallation = pandocState; + + return pandocState.IsAvailable; + } + + /// + /// Clears the cached Pandoc installation state. + /// Useful when the installation state might have changed. + /// + public void ClearCache() => this.cachedInstallation = null; +}