diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor index 8d0689da..8ea977ce 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor @@ -21,6 +21,7 @@ @@ -149,4 +150,4 @@ } } - + \ No newline at end of file diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs index 0dcb910c..ba909338 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs @@ -102,6 +102,7 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable private ElementReference mathContentContainer; private string lastMathRenderSignature = string.Empty; private bool hasActiveMathContainer; + private bool isOpeningAttachmentsDialog; private bool isDisposed; #region Overrides of ComponentBase @@ -600,8 +601,20 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable private async Task OpenAttachmentsDialog() { - var result = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.Content.FileAttachments.ToHashSet()); - this.Content.FileAttachments = result.ToList(); + if (this.isOpeningAttachmentsDialog) + return; + + this.isOpeningAttachmentsDialog = true; + + try + { + var result = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.Content.FileAttachments.ToHashSet()); + this.Content.FileAttachments = result.ToList(); + } + finally + { + this.isOpeningAttachmentsDialog = false; + } } public async ValueTask DisposeAsync() @@ -613,4 +626,4 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable await this.DisposeMathContainerIfNeededAsync(); this.Dispose(); } -} +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/AttachDocuments.razor b/app/MindWork AI Studio/Components/AttachDocuments.razor index bc66f9c2..aec9af94 100644 --- a/app/MindWork AI Studio/Components/AttachDocuments.razor +++ b/app/MindWork AI Studio/Components/AttachDocuments.razor @@ -6,11 +6,11 @@ @if (this.isDraggingOver) { + Content="@this.DocumentPaths.Count" + Color="Color.Primary" + Overlap="true" + Class="cursor-pointer" + OnClick="@this.OpenAttachmentsDialog"> @@ -35,6 +36,7 @@ @@ -45,6 +47,7 @@ } @@ -60,6 +63,7 @@ else Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Add" Color="Color.Primary" + Disabled="@(this.isOpeningFileDialog || this.isOpeningAttachmentsDialog)" OnClick="@(() => this.AddFilesManually())" Style="vertical-align: top; margin-top: -2px;" Size="Size.Small"> diff --git a/app/MindWork AI Studio/Components/AttachDocuments.razor.cs b/app/MindWork AI Studio/Components/AttachDocuments.razor.cs index acfc0dd2..23496b31 100644 --- a/app/MindWork AI Studio/Components/AttachDocuments.razor.cs +++ b/app/MindWork AI Studio/Components/AttachDocuments.razor.cs @@ -78,6 +78,8 @@ public partial class AttachDocuments : MSGComponentBase private uint numDropAreasAboveThis; private bool isComponentHovered; private bool isDraggingOver; + private bool isOpeningFileDialog; + private bool isOpeningAttachmentsDialog; #region Overrides of MSGComponentBase @@ -202,40 +204,64 @@ public partial class AttachDocuments : MSGComponentBase private async Task AddFilesManually() { + if (this.isOpeningFileDialog) + return; + + this.isOpeningFileDialog = true; + + try + { // Ensure that Pandoc is installed and ready: - var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync( - showSuccessMessage: false, - showDialog: true); + var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync( + showSuccessMessage: false, + showDialog: true); // If Pandoc is not available (user cancelled installation), abort file selection: - if (!pandocState.IsAvailable) - { - this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection."); - return; + if (!pandocState.IsAvailable) + { + this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection."); + return; + } + + var selectFiles = await this.RustService.SelectFiles(T("Select files to attach")); + if (selectFiles.UserCancelled) + return; + + foreach (var selectedFilePath in selectFiles.SelectedFilePaths) + { + if (!File.Exists(selectedFilePath)) + continue; + + if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, selectedFilePath, this.ValidateMediaFileTypes, this.Provider)) + continue; + + this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath)); + } + + await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths); + await this.OnChange(this.DocumentPaths); } - - var selectFiles = await this.RustService.SelectFiles(T("Select files to attach")); - if (selectFiles.UserCancelled) - return; - - foreach (var selectedFilePath in selectFiles.SelectedFilePaths) + finally { - if (!File.Exists(selectedFilePath)) - continue; - - if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, selectedFilePath, this.ValidateMediaFileTypes, this.Provider)) - continue; - - this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath)); + this.isOpeningFileDialog = false; } - - await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths); - await this.OnChange(this.DocumentPaths); } private async Task OpenAttachmentsDialog() { - this.DocumentPaths = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.DocumentPaths); + if (this.isOpeningAttachmentsDialog) + return; + + this.isOpeningAttachmentsDialog = true; + + try + { + this.DocumentPaths = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.DocumentPaths); + } + finally + { + this.isOpeningAttachmentsDialog = false; + } } private async Task ClearAllFiles() diff --git a/app/MindWork AI Studio/Components/ReadFileContent.razor b/app/MindWork AI Studio/Components/ReadFileContent.razor index 302224de..652e8610 100644 --- a/app/MindWork AI Studio/Components/ReadFileContent.razor +++ b/app/MindWork AI Studio/Components/ReadFileContent.razor @@ -1,5 +1,5 @@ @inherits MSGComponentBase - + @if (string.IsNullOrWhiteSpace(this.Text)) { @T("Use file content as input") @@ -8,4 +8,4 @@ { @this.Text } - + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs index d3248937..1ce488a9 100644 --- a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs +++ b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs @@ -30,53 +30,64 @@ public partial class ReadFileContent : MSGComponentBase [Inject] private PandocAvailabilityService PandocAvailabilityService { get; init; } = null!; + + private bool isSelectingFile; private async Task SelectFile() { - if (this.Disabled) + if (this.Disabled || this.isSelectingFile) return; - // Ensure that Pandoc is installed and ready: - var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync( - showSuccessMessage: false, - showDialog: true); - - // Check if Pandoc is available after the check / installation: - if (!pandocState.IsAvailable) - { - this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection."); - return; - } - - var selectedFile = await this.RustService.SelectFile(T("Select file to read its content")); - if (selectedFile.UserCancelled) - { - this.Logger.LogInformation("User cancelled the file selection"); - return; - } - - if(!File.Exists(selectedFile.SelectedFilePath)) - { - this.Logger.LogWarning("Selected file does not exist: '{FilePath}'", selectedFile.SelectedFilePath); - return; - } - - if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.DIRECTLY_LOADING_CONTENT, selectedFile.SelectedFilePath)) - { - this.Logger.LogWarning("User attempted to load unsupported file: {FilePath}", selectedFile.SelectedFilePath); - return; - } + this.isSelectingFile = true; try { - var fileContent = await UserFile.LoadFileData(selectedFile.SelectedFilePath, this.RustService, this.DialogService); - await this.FileContentChanged.InvokeAsync(fileContent); - this.Logger.LogInformation("Successfully loaded file content: {FilePath}", selectedFile.SelectedFilePath); + // Ensure that Pandoc is installed and ready: + var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync( + showSuccessMessage: false, + showDialog: true); + + // Check if Pandoc is available after the check / installation: + if (!pandocState.IsAvailable) + { + this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection."); + return; + } + + var selectedFile = await this.RustService.SelectFile(T("Select file to read its content")); + if (selectedFile.UserCancelled) + { + this.Logger.LogInformation("User cancelled the file selection"); + return; + } + + if(!File.Exists(selectedFile.SelectedFilePath)) + { + this.Logger.LogWarning("Selected file does not exist: '{FilePath}'", selectedFile.SelectedFilePath); + return; + } + + if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.DIRECTLY_LOADING_CONTENT, selectedFile.SelectedFilePath)) + { + this.Logger.LogWarning("User attempted to load unsupported file: {FilePath}", selectedFile.SelectedFilePath); + return; + } + + try + { + var fileContent = await UserFile.LoadFileData(selectedFile.SelectedFilePath, this.RustService, this.DialogService); + await this.FileContentChanged.InvokeAsync(fileContent); + this.Logger.LogInformation("Successfully loaded file content: {FilePath}", selectedFile.SelectedFilePath); + } + catch (Exception ex) + { + this.Logger.LogError(ex, "Failed to load file content: {FilePath}", selectedFile.SelectedFilePath); + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, T("Failed to load file content"))); + } } - catch (Exception ex) + finally { - this.Logger.LogError(ex, "Failed to load file content: {FilePath}", selectedFile.SelectedFilePath); - await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, T("Failed to load file content"))); + this.isSelectingFile = false; } } -} +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/SelectFile.razor b/app/MindWork AI Studio/Components/SelectFile.razor index de3971e5..bf05dfec 100644 --- a/app/MindWork AI Studio/Components/SelectFile.razor +++ b/app/MindWork AI Studio/Components/SelectFile.razor @@ -13,7 +13,7 @@ Variant="Variant.Outlined" /> - + @T("Choose File") \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/SelectFile.razor.cs b/app/MindWork AI Studio/Components/SelectFile.razor.cs index 91c7a667..4c7eb166 100644 --- a/app/MindWork AI Studio/Components/SelectFile.razor.cs +++ b/app/MindWork AI Studio/Components/SelectFile.razor.cs @@ -35,6 +35,7 @@ public partial class SelectFile : MSGComponentBase protected ILogger Logger { get; init; } = null!; private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); + private bool isOpeningFileDialog; #region Overrides of ComponentBase @@ -55,10 +56,22 @@ public partial class SelectFile : MSGComponentBase private async Task OpenFileDialog() { - var response = await this.RustService.SelectFile(this.FileDialogTitle, this.Filter, string.IsNullOrWhiteSpace(this.File) ? null : this.File); - this.Logger.LogInformation($"The user selected the file '{response.SelectedFilePath}'."); + if (this.Disabled || this.isOpeningFileDialog) + return; - if (!response.UserCancelled) - this.InternalFileChanged(response.SelectedFilePath); + this.isOpeningFileDialog = true; + + try + { + var response = await this.RustService.SelectFile(this.FileDialogTitle, this.Filter, string.IsNullOrWhiteSpace(this.File) ? null : this.File); + this.Logger.LogInformation($"The user selected the file '{response.SelectedFilePath}'."); + + if (!response.UserCancelled) + this.InternalFileChanged(response.SelectedFilePath); + } + finally + { + this.isOpeningFileDialog = false; + } } } \ No newline at end of file