From 3439b91c284b9b739b32f0f12d6ea830a3f859ab Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 9 Sep 2026 17:10:24 +0200 Subject: [PATCH] Allow choosing a RAG folder or file by dropping it --- .../AssistantBatchProcessing.razor | 8 ++- .../Assistants/ERI/AssistantERI.razor | 2 +- .../Assistants/I18N/allTexts.lua | 21 +++++++ .../Components/SelectDirectory.razor | 52 ++++++++++----- .../Components/SelectDirectory.razor.cs | 56 +++++++++++++++++ .../Components/SelectFile.razor | 52 ++++++++++----- .../Components/SelectFile.razor.cs | 63 +++++++++++++++++++ .../DataSourceLocalDirectoryDialog.razor | 2 +- .../Dialogs/DataSourceLocalFileDialog.razor | 2 +- .../plugin.lua | 21 +++++++ .../plugin.lua | 21 +++++++ 11 files changed, 260 insertions(+), 40 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor b/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor index 7e9429d9..c0099c70 100644 --- a/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor +++ b/app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor @@ -7,7 +7,9 @@ @T("Input") - +@* This page carries a second drop zone for the instructions, so neither of them catches all + documents: with two targets, only the one under the mouse may take the drop. *@ + @@ -44,7 +46,7 @@ @if (this.promptSource is BatchProcessingPromptSource.FREE_PROMPT) { - + @@ -52,7 +54,7 @@ } else if (this.promptSource is BatchProcessingPromptSource.FILE_IMPORT) { - + @if (!string.IsNullOrWhiteSpace(this.promptFilePath)) { diff --git a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor index 112b6820..15a39c90 100644 --- a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor +++ b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor @@ -345,4 +345,4 @@ else - + diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index d753b8ec..003ff2a0 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -4291,12 +4291,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SECRETINPUTFIELD::T1273315904"] = "Hide c -- Show content UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SECRETINPUTFIELD::T2891011873"] = "Show content" +-- The dropped folder could not be accessed. Please choose it with the folder chooser instead. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T1153417816"] = "The dropped folder could not be accessed. Please choose it with the folder chooser instead." + +-- Please drop a folder, not a file. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T3289690493"] = "Please drop a folder, not a file." + +-- You can also drag & drop the folder here. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T350096725"] = "You can also drag & drop the folder here." + -- Choose Directory UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T4256489763"] = "Choose Directory" +-- Please drop a file, not a folder. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T1472251601"] = "Please drop a file, not a folder." + +-- You can also drag & drop the file here. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T1984243691"] = "You can also drag & drop the file here." + -- Choose File UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T4285779702"] = "Choose File" +-- Please drop a file with a supported file type. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T930441004"] = "Please drop a file with a supported file type." + +-- The dropped file could not be accessed. Please choose it with the file chooser instead. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T984660028"] = "The dropped file could not be accessed. Please choose it with the file chooser instead." + -- External Assistants rated below this audit level are treated as insufficiently reviewed. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAGENTASSISTANTAUDIT::T1162151451"] = "External Assistants rated below this audit level are treated as insufficiently reviewed." diff --git a/app/MindWork AI Studio/Components/SelectDirectory.razor b/app/MindWork AI Studio/Components/SelectDirectory.razor index 096db371..b078d474 100644 --- a/app/MindWork AI Studio/Components/SelectDirectory.razor +++ b/app/MindWork AI Studio/Components/SelectDirectory.razor @@ -1,19 +1,37 @@ @inherits MSGComponentBase - - - - - @T("Choose Directory") - - \ No newline at end of file +@if (this.EnableDragDrop) +{ + + @this.Picker + + @T("You can also drag & drop the folder here.") + + +} +else +{ + @this.Picker +} + +@code { + + private RenderFragment Picker => + @ + + + + @T("Choose Directory") + + ; +} diff --git a/app/MindWork AI Studio/Components/SelectDirectory.razor.cs b/app/MindWork AI Studio/Components/SelectDirectory.razor.cs index 6f576435..883d5277 100644 --- a/app/MindWork AI Studio/Components/SelectDirectory.razor.cs +++ b/app/MindWork AI Studio/Components/SelectDirectory.razor.cs @@ -2,6 +2,9 @@ using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; +// This component has a parameter called Directory, which would shadow the file system's Directory class: +using IODirectory = System.IO.Directory; + namespace AIStudio.Components; public partial class SelectDirectory : MSGComponentBase @@ -24,6 +27,24 @@ public partial class SelectDirectory : MSGComponentBase [Parameter] public Func Validation { get; set; } = _ => null; + /// + /// When true, the folder can also be chosen by dropping it onto this component. + /// + [Parameter] + public bool EnableDragDrop { get; set; } + + /// + /// On which layer to register the drop area. Higher layers have priority over lower layers. + /// + [Parameter] + public int Layer { get; set; } = DropLayers.ROOT; + + /// + /// Catch all documents that are hovered over the AI Studio window and not only over the drop zone. + /// + [Parameter] + public bool CatchAllDocuments { get; set; } + [Inject] public RustService RustService { get; set; } = null!; @@ -69,4 +90,39 @@ public partial class SelectDirectory : MSGComponentBase this.isDirectoryDialogOpen = false; } } + + /// + /// Takes the first dropped path which leads to a folder. + /// + /// + /// A dropped file is rejected instead of being taken as its parent folder. Everything a folder + /// contains is processed, so guessing the parent of a mistakenly dropped file could pull in far + /// more data than the user meant to hand over. + /// + /// The dropped paths. + private async Task PathsDropped(List paths) + { + foreach (var path in paths) + { + if (!IODirectory.Exists(path)) + continue; + + this.Logger.LogInformation("The user dropped the directory '{DroppedDirectory}'.", path); + this.InternalDirectoryChanged(path); + return; + } + + this.Logger.LogWarning("None of the {Count} dropped path(s) could be used as a directory.", paths.Count); + await this.MessageBus.SendWarning(new(Icons.Material.Filled.Warning, this.GetDropWarning(paths))); + } + + private string GetDropWarning(List paths) + { + // Naming the actual mistake beats a generic "that did not work". Dropping a file onto a + // folder picker is the likeliest of them: + if (paths.Any(File.Exists)) + return T("Please drop a folder, not a file."); + + return T("The dropped folder could not be accessed. Please choose it with the folder chooser instead."); + } } \ 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 726965fd..4df232b8 100644 --- a/app/MindWork AI Studio/Components/SelectFile.razor +++ b/app/MindWork AI Studio/Components/SelectFile.razor @@ -1,19 +1,37 @@ @inherits MSGComponentBase - - - - - @T("Choose File") - - \ No newline at end of file +@if (this.EnableDragDrop) +{ + + @this.Picker + + @T("You can also drag & drop the file here.") + + +} +else +{ + @this.Picker +} + +@code { + + private RenderFragment Picker => + @ + + + + @T("Choose File") + + ; +} diff --git a/app/MindWork AI Studio/Components/SelectFile.razor.cs b/app/MindWork AI Studio/Components/SelectFile.razor.cs index de1f89a3..124f104f 100644 --- a/app/MindWork AI Studio/Components/SelectFile.razor.cs +++ b/app/MindWork AI Studio/Components/SelectFile.razor.cs @@ -3,6 +3,9 @@ using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; +// This component has a parameter called File, which would shadow the file system's File class: +using IOFile = System.IO.File; + namespace AIStudio.Components; public partial class SelectFile : MSGComponentBase @@ -28,6 +31,24 @@ public partial class SelectFile : MSGComponentBase [Parameter] public Func Validation { get; set; } = _ => null; + /// + /// When true, the file can also be chosen by dropping it onto this component. + /// + [Parameter] + public bool EnableDragDrop { get; set; } + + /// + /// On which layer to register the drop area. Higher layers have priority over lower layers. + /// + [Parameter] + public int Layer { get; set; } = DropLayers.ROOT; + + /// + /// Catch all documents that are hovered over the AI Studio window and not only over the drop zone. + /// + [Parameter] + public bool CatchAllDocuments { get; set; } + [Inject] public RustService RustService { get; set; } = null!; @@ -73,4 +94,46 @@ public partial class SelectFile : MSGComponentBase this.isFileDialogOpen = false; } } + + /// + /// Takes the first dropped path which leads to a usable file. + /// + /// + /// This component carries exactly one file, so a multi-selection cannot be honored as a whole. + /// A dropped folder is rejected instead of being read as "the first file inside it": the user + /// was asked for a file, and picking one for them would be a surprise. + /// + /// The dropped paths. + private async Task PathsDropped(List paths) + { + foreach (var path in paths) + { + if (!IOFile.Exists(path)) + continue; + + if (this.Filter is { Length: > 0 } && !FileTypes.IsAllowedPath(path, this.Filter)) + continue; + + this.Logger.LogInformation("The user dropped the file '{DroppedFilePath}'.", path); + this.InternalFileChanged(path); + return; + } + + this.Logger.LogWarning("None of the {Count} dropped path(s) could be used as a file.", paths.Count); + await this.MessageBus.SendWarning(new(Icons.Material.Filled.Warning, this.GetDropWarning(paths))); + } + + private string GetDropWarning(List paths) + { + // Naming the actual mistake beats a generic "that did not work". Dropping a folder onto a + // file picker is the likeliest of them: + if (paths.Any(Directory.Exists)) + return T("Please drop a file, not a folder."); + + // The file exists, so the file type filter is what turned it down: + if (paths.Any(IOFile.Exists)) + return T("Please drop a file with a supported file type."); + + return T("The dropped file could not be accessed. Please choose it with the file chooser instead."); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor index f80a98a9..9679ead3 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor +++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor @@ -51,7 +51,7 @@ } @if (this.CanChangeSourceAndEmbedding) { - + } else { diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor index 8f3cb2e3..965a51f6 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor +++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor @@ -51,7 +51,7 @@ } @if (this.CanChangeSourceAndEmbedding) { - + } else { diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index 42057668..abf0e349 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -4293,12 +4293,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SECRETINPUTFIELD::T1273315904"] = "Inhalt -- Show content UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SECRETINPUTFIELD::T2891011873"] = "Inhalt anzeigen" +-- The dropped folder could not be accessed. Please choose it with the folder chooser instead. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T1153417816"] = "Auf den abgelegten Ordner konnte nicht zugegriffen werden. Bitte wählen Sie ihn stattdessen über die Ordnerauswahl aus." + +-- Please drop a folder, not a file. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T3289690493"] = "Bitte legen Sie einen Ordner ab, keine Datei." + +-- You can also drag & drop the folder here. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T350096725"] = "Sie können den Ordner auch hierher ziehen und ablegen." + -- Choose Directory UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T4256489763"] = "Verzeichnis auswählen" +-- Please drop a file, not a folder. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T1472251601"] = "Bitte legen Sie eine Datei ab, keinen Ordner." + +-- You can also drag & drop the file here. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T1984243691"] = "Sie können die Datei auch hierher ziehen und ablegen." + -- Choose File UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T4285779702"] = "Datei auswählen" +-- Please drop a file with a supported file type. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T930441004"] = "Bitte legen Sie eine Datei mit einem unterstützten Dateityp ab." + +-- The dropped file could not be accessed. Please choose it with the file chooser instead. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T984660028"] = "Auf die abgelegte Datei konnte nicht zugegriffen werden. Bitte wählen Sie sie stattdessen über die Dateiauswahl aus." + -- External Assistants rated below this audit level are treated as insufficiently reviewed. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAGENTASSISTANTAUDIT::T1162151451"] = "Externe Assistenten, die unter diesem Audit Level bewertet werden, gelten als nicht ausreichend sicher." diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 7b26d329..00fd7658 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -4293,12 +4293,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SECRETINPUTFIELD::T1273315904"] = "Hide c -- Show content UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SECRETINPUTFIELD::T2891011873"] = "Show content" +-- The dropped folder could not be accessed. Please choose it with the folder chooser instead. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T1153417816"] = "The dropped folder could not be accessed. Please choose it with the folder chooser instead." + +-- Please drop a folder, not a file. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T3289690493"] = "Please drop a folder, not a file." + +-- You can also drag & drop the folder here. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T350096725"] = "You can also drag & drop the folder here." + -- Choose Directory UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTDIRECTORY::T4256489763"] = "Choose Directory" +-- Please drop a file, not a folder. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T1472251601"] = "Please drop a file, not a folder." + +-- You can also drag & drop the file here. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T1984243691"] = "You can also drag & drop the file here." + -- Choose File UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T4285779702"] = "Choose File" +-- Please drop a file with a supported file type. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T930441004"] = "Please drop a file with a supported file type." + +-- The dropped file could not be accessed. Please choose it with the file chooser instead. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SELECTFILE::T984660028"] = "The dropped file could not be accessed. Please choose it with the file chooser instead." + -- External Assistants rated below this audit level are treated as insufficiently reviewed. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAGENTASSISTANTAUDIT::T1162151451"] = "External Assistants rated below this audit level are treated as insufficiently reviewed."