Inject PandocAvailabilityService and refactor its usage

This commit is contained in:
Thorsten Sommer 2025-12-10 13:27:24 +01:00
parent 85e0d7dd6c
commit bb843fa812
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 109 additions and 26 deletions

View File

@ -82,8 +82,11 @@
}
<ChatTemplateSelection CanChatThreadBeUsedForTemplate="@this.CanThreadBeSaved" CurrentChatThread="@this.ChatThread" CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/>
<AttachDocuments Name="File Attachments" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true"/>
@if (this.isPandocAvailable)
{
<AttachDocuments Name="File Attachments" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true"/>
}
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
{

View File

@ -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<ChatComponent> 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<string, object?> USER_INPUT_ATTRIBUTES = new();
@ -58,6 +62,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private Guid currentWorkspaceId = Guid.Empty;
private CancellationTokenSource? cancellationTokenSource;
private HashSet<string> 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();
}

View File

@ -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<ReadFileContent> 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<PandocDialog>
{
{ x => x.ShowInitialResultInSnackbar, false },
};
var dialogReference = await this.DialogService.ShowAsync<PandocDialog>(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);

View File

@ -126,6 +126,7 @@ internal sealed class Program
builder.Services.AddSingleton<SettingsManager>();
builder.Services.AddSingleton<ThreadSafeRandom>();
builder.Services.AddSingleton<DataSourceService>();
builder.Services.AddScoped<PandocAvailabilityService>();
builder.Services.AddTransient<HTMLParser>();
builder.Services.AddTransient<AgentDataSourceSelection>();
builder.Services.AddTransient<AgentRetrievalContextValidation>();

View File

@ -0,0 +1,85 @@
using AIStudio.Dialogs;
using AIStudio.Tools.PluginSystem;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Tools.Services;
/// <summary>
/// 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.
/// </summary>
public sealed class PandocAvailabilityService(RustService rustService, IDialogService dialogService, ILogger<PandocAvailabilityService> 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<PandocAvailabilityService> Logger => logger;
private PandocInstallation? cachedInstallation;
/// <summary>
/// Checks if Pandoc is available and shows the installation dialog if needed.
/// </summary>
/// <param name="showSuccessMessage">Whether to show a success message if Pandoc is available.</param>
/// <param name="showDialog">Whether to show the installation dialog if Pandoc is not available.</param>
/// <returns>The Pandoc installation state.</returns>
public async Task<PandocInstallation> 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<PandocDialog>
{
{ x => x.ShowInitialResultInSnackbar, false },
};
var dialogReference = await this.DialogService.ShowAsync<PandocDialog>(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;
}
/// <summary>
/// Checks if Pandoc is available without showing any dialogs or messages.
/// Uses cached result if available to avoid redundant checks.
/// </summary>
/// <returns>True if Pandoc is available, false otherwise.</returns>
public async Task<bool> 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;
}
/// <summary>
/// Clears the cached Pandoc installation state.
/// Useful when the installation state might have changed.
/// </summary>
public void ClearCache() => this.cachedInstallation = null;
}