mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 17:32:11 +00:00
Improved batch input handling
This commit is contained in:
parent
59c952e7d4
commit
cd8ec4cbb9
@ -1,6 +1,7 @@
|
|||||||
@attribute [Route(Routes.ASSISTANT_BATCH_PROCESSING)]
|
@attribute [Route(Routes.ASSISTANT_BATCH_PROCESSING)]
|
||||||
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogBatchProcessing>
|
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogBatchProcessing>
|
||||||
@using AIStudio.Settings.DataModel
|
@using AIStudio.Settings.DataModel
|
||||||
|
@using AIStudio.Tools.Rust
|
||||||
|
|
||||||
<MudText Typo="Typo.h5" Class="mb-3">
|
<MudText Typo="Typo.h5" Class="mb-3">
|
||||||
@T("Input")
|
@T("Input")
|
||||||
@ -12,6 +13,13 @@
|
|||||||
|
|
||||||
<MudTextSwitch Label="@T("Include subfolders?")" Disabled="@this.isProcessingBatch" Value="@this.includeSubdirectories" ValueChanged="@(v => this.includeSubdirectories = v)" LabelOn="@T("Yes, process files in subfolders as well")" LabelOff="@T("No, only process files in the selected folder")"/>
|
<MudTextSwitch Label="@T("Include subfolders?")" Disabled="@this.isProcessingBatch" Value="@this.includeSubdirectories" ValueChanged="@(v => this.includeSubdirectories = v)" LabelOn="@T("Yes, process files in subfolders as well")" LabelOff="@T("No, only process files in the selected folder")"/>
|
||||||
|
|
||||||
|
@if (this.includeSubdirectories)
|
||||||
|
{
|
||||||
|
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
|
||||||
|
@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.")
|
||||||
|
</MudJustifiedText>
|
||||||
|
}
|
||||||
|
|
||||||
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
|
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
|
||||||
@T("Instructions")
|
@T("Instructions")
|
||||||
</MudText>
|
</MudText>
|
||||||
@ -27,11 +35,13 @@
|
|||||||
|
|
||||||
@if (this.promptSource is BatchProcessingPromptSource.FREE_PROMPT)
|
@if (this.promptSource is BatchProcessingPromptSource.FREE_PROMPT)
|
||||||
{
|
{
|
||||||
|
<ReadFileContent Text="@T("Load prompt from file")" @bind-FileContent="@this.freePrompt" Disabled="@this.isProcessingBatch"/>
|
||||||
|
|
||||||
<MudTextField T="string" @bind-Text="@this.freePrompt" Validation="@this.ValidateFreePrompt" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("What should the AI do with each document?")" HelperText="@T("These instructions are applied to every single document of the batch run.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
<MudTextField T="string" @bind-Text="@this.freePrompt" Validation="@this.ValidateFreePrompt" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("What should the AI do with each document?")" HelperText="@T("These instructions are applied to every single document of the batch run.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||||
}
|
}
|
||||||
else if (this.promptSource is BatchProcessingPromptSource.FILE_IMPORT)
|
else if (this.promptSource is BatchProcessingPromptSource.FILE_IMPORT)
|
||||||
{
|
{
|
||||||
<ReadFileContent Text="@T("Select the file with your instructions")" @bind-FileContent="@this.ImportedPrompt" ShowAttachedDocumentState="@true" Disabled="@this.isProcessingBatch"/>
|
<ReadFileContent Text="@T("Select the file with your instructions")" @bind-FileContent="@this.ImportedPrompt" Filter="@([FileTypes.MARKDOWN])" ShowAttachedDocumentState="@true" Disabled="@this.isProcessingBatch"/>
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(this.promptFilePath))
|
@if (!string.IsNullOrWhiteSpace(this.promptFilePath))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,6 +20,25 @@ public partial class AssistantBatchProcessing
|
|||||||
if (string.IsNullOrWhiteSpace(patterns))
|
if (string.IsNullOrWhiteSpace(patterns))
|
||||||
return T("Please provide at least one file pattern, e.g., *.pdf. Separate multiple patterns with a semicolon.");
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,13 @@ public partial class ReadFileContent : MSGComponentBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool CatchAllDocuments { get; set; }
|
public bool CatchAllDocuments { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optionally restricts the file types offered by the native file picker
|
||||||
|
/// and accepted by this component.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public FileTypeFilter[]? Filter { get; set; }
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private RustService RustService { get; init; } = null!;
|
private RustService RustService { get; init; } = null!;
|
||||||
@ -252,7 +259,7 @@ public partial class ReadFileContent : MSGComponentBase
|
|||||||
this.isFileDialogOpen = true;
|
this.isFileDialogOpen = true;
|
||||||
try
|
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)
|
if (selectedFile.UserCancelled)
|
||||||
{
|
{
|
||||||
this.Logger.LogInformation("User cancelled the file selection");
|
this.Logger.LogInformation("User cancelled the file selection");
|
||||||
@ -310,6 +317,13 @@ public partial class ReadFileContent : MSGComponentBase
|
|||||||
return false;
|
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))
|
if (FileTypes.IsAllowedPath(filePath, FileTypes.AUDIO) || FileTypes.IsAllowedPath(filePath, FileTypes.VIDEO))
|
||||||
return await this.LoadMediaTranscriptAsync(filePath);
|
return await this.LoadMediaTranscriptAsync(filePath);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
@using AIStudio.Assistants.BatchProcessing
|
@using AIStudio.Assistants.BatchProcessing
|
||||||
@using AIStudio.Settings
|
@using AIStudio.Settings
|
||||||
|
@using AIStudio.Tools.Rust
|
||||||
@inherits SettingsDialogBase
|
@inherits SettingsDialogBase
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
@ -26,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
else if (this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource is BatchProcessingPromptSource.FILE_IMPORT)
|
else if (this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource is BatchProcessingPromptSource.FILE_IMPORT)
|
||||||
{
|
{
|
||||||
<ConfigurationText OptionDescription="@T("Default Markdown instructions file")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Description" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.PromptFilePath)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.PromptFilePath = value)" OptionHelp="@T("The current content of this Markdown file is loaded whenever the defaults are applied.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PromptFilePath, out var meta) && meta.IsLocked"/>
|
<ConfigurationFile OptionDescription="@T("Default Markdown instructions file")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Description" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.PromptFilePath)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.PromptFilePath = value)" FileDialogTitle="@T("Select the default Markdown instructions file")" Filter="@([FileTypes.MARKDOWN])" OptionHelp="@T("The current content of this Markdown file is loaded whenever the defaults are applied.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PromptFilePath, out var meta) && meta.IsLocked"/>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -54,4 +55,4 @@
|
|||||||
<DialogActions>
|
<DialogActions>
|
||||||
<MudButton OnClick="@this.Close" Variant="Variant.Filled">@T("Close")</MudButton>
|
<MudButton OnClick="@this.Close" Variant="Variant.Filled">@T("Close")</MudButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</MudDialog>
|
</MudDialog>
|
||||||
|
|||||||
@ -50,6 +50,7 @@ public static class FileTypes
|
|||||||
|
|
||||||
// Document hierarchy
|
// Document hierarchy
|
||||||
public static readonly FileTypeFilter PDF = FileTypeFilter.Leaf("PDF", "pdf");
|
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 TEXT = FileTypeFilter.Leaf(TB("Text"), "txt", "md", "rtf");
|
||||||
public static readonly FileTypeFilter TABULAR = FileTypeFilter.Leaf(TB("Tabular text"), "csv", "tsv");
|
public static readonly FileTypeFilter TABULAR = FileTypeFilter.Leaf(TB("Tabular text"), "csv", "tsv");
|
||||||
public static readonly FileTypeFilter MS_WORD = FileTypeFilter.Leaf("Microsoft Word", "docx");
|
public static readonly FileTypeFilter MS_WORD = FileTypeFilter.Leaf("Microsoft Word", "docx");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user