From cd8ec4cbb9744b01bcadb3e6d074a9518244e40e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 11 Aug 2026 12:58:58 +0200 Subject: [PATCH] Improved batch input handling --- .../AssistantBatchProcessing.razor | 12 +++++++++++- ...sistantBatchProcessing.razor.Validation.cs | 19 +++++++++++++++++++ .../Components/ReadFileContent.razor.cs | 16 +++++++++++++++- .../SettingsDialogBatchProcessing.razor | 5 +++-- .../Tools/Rust/FileTypes.cs | 1 + 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor b/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor index 6cb03bc1..73e40fd6 100644 --- a/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor +++ b/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor @@ -1,6 +1,7 @@ @attribute [Route(Routes.ASSISTANT_BATCH_PROCESSING)] @inherits AssistantBaseCore @using AIStudio.Settings.DataModel +@using AIStudio.Tools.Rust @T("Input") @@ -12,6 +13,13 @@ +@if (this.includeSubdirectories) +{ + + @T("A separate output folder is excluded from document discovery. This includes the default 'ai-results' folder, so results from an earlier run are not processed again. If the input folder itself is used for output, known batch result files are excluded instead.") + +} + @T("Instructions") @@ -27,11 +35,13 @@ @if (this.promptSource is BatchProcessingPromptSource.FREE_PROMPT) { + + } else if (this.promptSource is BatchProcessingPromptSource.FILE_IMPORT) { - + @if (!string.IsNullOrWhiteSpace(this.promptFilePath)) { diff --git a/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor.Validation.cs b/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor.Validation.cs index 9197feff..80cf156d 100644 --- a/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor.Validation.cs +++ b/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor.Validation.cs @@ -20,6 +20,25 @@ public partial class AssistantBatchProcessing if (string.IsNullOrWhiteSpace(patterns)) return T("Please provide at least one file pattern, e.g., *.pdf. Separate multiple patterns with a semicolon."); + var individualPatterns = patterns.Split(';'); + if (individualPatterns.Any(string.IsNullOrWhiteSpace)) + return T("Please remove empty file patterns. Separate valid patterns with a single semicolon."); + + foreach (var patternEntry in individualPatterns) + { + var pattern = patternEntry.Trim(); + if (pattern is "." or ".." + || pattern.EndsWith("..", StringComparison.Ordinal) + || pattern.IndexOfAny([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, '/', '\\']) >= 0) + return T("Please use file name patterns without folder paths, e.g., *.pdf or report-*.docx."); + + var invalidCharacters = Path.GetInvalidFileNameChars() + .Where(character => character is not '*' and not '?') + .ToArray(); + if (pattern.IndexOfAny(invalidCharacters) >= 0) + return T("One of the file patterns contains an invalid character."); + } + return null; } diff --git a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs index 1e4b6890..2ef38d1f 100644 --- a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs +++ b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs @@ -50,6 +50,13 @@ public partial class ReadFileContent : MSGComponentBase /// [Parameter] public bool CatchAllDocuments { get; set; } + + /// + /// Optionally restricts the file types offered by the native file picker + /// and accepted by this component. + /// + [Parameter] + public FileTypeFilter[]? Filter { get; set; } [Inject] private RustService RustService { get; init; } = null!; @@ -252,7 +259,7 @@ public partial class ReadFileContent : MSGComponentBase this.isFileDialogOpen = true; try { - var selectedFile = await this.RustService.SelectFile(T("Select file to read its content")); + var selectedFile = await this.RustService.SelectFile(T("Select file to read its content"), this.Filter); if (selectedFile.UserCancelled) { this.Logger.LogInformation("User cancelled the file selection"); @@ -310,6 +317,13 @@ public partial class ReadFileContent : MSGComponentBase return false; } + if (this.Filter is { Length: > 0 } && !FileTypes.IsAllowedPath(filePath, this.Filter)) + { + this.Logger.LogWarning("Selected file does not match the configured file type filter: '{FilePath}'", filePath); + await this.MessageBus.SendWarning(new(Icons.Material.Filled.Warning, this.T("Please select a file with a supported file type."))); + return false; + } + if (FileTypes.IsAllowedPath(filePath, FileTypes.AUDIO) || FileTypes.IsAllowedPath(filePath, FileTypes.VIDEO)) return await this.LoadMediaTranscriptAsync(filePath); diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBatchProcessing.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBatchProcessing.razor index 7d430e36..c2bfd512 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBatchProcessing.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBatchProcessing.razor @@ -1,5 +1,6 @@ @using AIStudio.Assistants.BatchProcessing @using AIStudio.Settings +@using AIStudio.Tools.Rust @inherits SettingsDialogBase @@ -26,7 +27,7 @@ } else if (this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource is BatchProcessingPromptSource.FILE_IMPORT) { - + } else { @@ -54,4 +55,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Tools/Rust/FileTypes.cs b/app/MindWork AI Studio/Tools/Rust/FileTypes.cs index aa71bda8..69d71fe2 100644 --- a/app/MindWork AI Studio/Tools/Rust/FileTypes.cs +++ b/app/MindWork AI Studio/Tools/Rust/FileTypes.cs @@ -50,6 +50,7 @@ public static class FileTypes // Document hierarchy public static readonly FileTypeFilter PDF = FileTypeFilter.Leaf("PDF", "pdf"); + public static readonly FileTypeFilter MARKDOWN = FileTypeFilter.Leaf("Markdown", "md"); public static readonly FileTypeFilter TEXT = FileTypeFilter.Leaf(TB("Text"), "txt", "md", "rtf"); public static readonly FileTypeFilter TABULAR = FileTypeFilter.Leaf(TB("Tabular text"), "csv", "tsv"); public static readonly FileTypeFilter MS_WORD = FileTypeFilter.Leaf("Microsoft Word", "docx");