mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-08 10:29:39 +00:00
Allow choosing a RAG folder or file by dropping it
This commit is contained in:
11 files changed
+260
-40
No files matched your search
@@ -1,19 +1,37 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
|
||||
<MudTextField
|
||||
T="string"
|
||||
Text="@this.Directory"
|
||||
Label="@this.Label"
|
||||
ReadOnly="@true"
|
||||
Validation="@this.Validation"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Folder"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
Variant="Variant.Outlined"
|
||||
/>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.Disabled || this.isDirectoryDialogOpen)" OnClick="@this.OpenDirectoryDialog">
|
||||
@T("Choose Directory")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
@if (this.EnableDragDrop)
|
||||
{
|
||||
<PathDropZone Layer="@this.Layer" CatchAllDocuments="@this.CatchAllDocuments" Disabled="@this.Disabled" OnPathsDropped="@this.PathsDropped">
|
||||
@this.Picker
|
||||
<MudText Typo="Typo.body2">
|
||||
@T("You can also drag & drop the folder here.")
|
||||
</MudText>
|
||||
</PathDropZone>
|
||||
}
|
||||
else
|
||||
{
|
||||
@this.Picker
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
private RenderFragment Picker =>
|
||||
@<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
|
||||
<MudTextField
|
||||
T="string"
|
||||
Text="@this.Directory"
|
||||
Label="@this.Label"
|
||||
ReadOnly="@true"
|
||||
Validation="@this.Validation"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Folder"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
Variant="Variant.Outlined"
|
||||
/>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.Disabled || this.isDirectoryDialogOpen)" OnClick="@this.OpenDirectoryDialog">
|
||||
@T("Choose Directory")
|
||||
</MudButton>
|
||||
</MudStack>;
|
||||
}
|
||||
@@ -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<string, string?> Validation { get; set; } = _ => null;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the folder can also be chosen by dropping it onto this component.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool EnableDragDrop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// On which layer to register the drop area. Higher layers have priority over lower layers.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Layer { get; set; } = DropLayers.ROOT;
|
||||
|
||||
/// <summary>
|
||||
/// Catch all documents that are hovered over the AI Studio window and not only over the drop zone.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the first dropped path which leads to a folder.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="paths">The dropped paths.</param>
|
||||
private async Task PathsDropped(List<string> 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<string> 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.");
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,37 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
|
||||
<MudTextField
|
||||
T="string"
|
||||
Text="@this.File"
|
||||
Label="@this.Label"
|
||||
ReadOnly="@true"
|
||||
Validation="@this.Validation"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.AttachFile"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
Variant="Variant.Outlined"
|
||||
/>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.Disabled || this.isFileDialogOpen)" OnClick="@this.OpenFileDialog">
|
||||
@T("Choose File")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
@if (this.EnableDragDrop)
|
||||
{
|
||||
<PathDropZone Layer="@this.Layer" CatchAllDocuments="@this.CatchAllDocuments" Disabled="@this.Disabled" OnPathsDropped="@this.PathsDropped">
|
||||
@this.Picker
|
||||
<MudText Typo="Typo.body2">
|
||||
@T("You can also drag & drop the file here.")
|
||||
</MudText>
|
||||
</PathDropZone>
|
||||
}
|
||||
else
|
||||
{
|
||||
@this.Picker
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
private RenderFragment Picker =>
|
||||
@<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
|
||||
<MudTextField
|
||||
T="string"
|
||||
Text="@this.File"
|
||||
Label="@this.Label"
|
||||
ReadOnly="@true"
|
||||
Validation="@this.Validation"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.AttachFile"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
Variant="Variant.Outlined"
|
||||
/>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.Disabled || this.isFileDialogOpen)" OnClick="@this.OpenFileDialog">
|
||||
@T("Choose File")
|
||||
</MudButton>
|
||||
</MudStack>;
|
||||
}
|
||||
@@ -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<string, string?> Validation { get; set; } = _ => null;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the file can also be chosen by dropping it onto this component.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool EnableDragDrop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// On which layer to register the drop area. Higher layers have priority over lower layers.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Layer { get; set; } = DropLayers.ROOT;
|
||||
|
||||
/// <summary>
|
||||
/// Catch all documents that are hovered over the AI Studio window and not only over the drop zone.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the first dropped path which leads to a usable file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="paths">The dropped paths.</param>
|
||||
private async Task PathsDropped(List<string> 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<string> 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.");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user