Added the Batch Processing Assistant (#901)
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
j-erlerandThorsten Sommer authored and GitHub committed 2026-08-11 20:14:21 +02:00
1 parent 6eab9dc574
commit bb8f6f13f0
48 files changed
+4402 -39

No files matched your search

@@ -0,0 +1,27 @@
@inherits ConfigurationBaseCore
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudTextField
T="string"
Text="@this.Text()"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="Adornment.Start"
AdornmentIcon="@this.Icon"
AdornmentColor="@this.IconColor"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Immediate="@true"
Underline="false"
Class="flex-grow-1"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen"
Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Small"
Disabled="@(this.IsDisabled || this.isDirectoryDialogOpen)"
Class="mb-1"
OnClick="@this.OpenDirectoryDialog">
@T("Choose Directory")
</MudButton>
</MudStack>
@@ -0,0 +1,133 @@
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
using Timer = System.Timers.Timer;
namespace AIStudio.Components;
public partial class ConfigurationDirectory : ConfigurationBaseCore
{
/// <summary>
/// The text used for the textfield.
/// </summary>
[Parameter]
public Func<string> Text { get; set; } = () => string.Empty;
/// <summary>
/// An action which is called when the text was changed.
/// </summary>
[Parameter]
public Action<string> TextUpdate { get; set; } = _ => { };
/// <summary>
/// The icon to display next to the textfield.
/// </summary>
[Parameter]
public string Icon { get; set; } = Icons.Material.Filled.Folder;
/// <summary>
/// The color of the icon to use.
/// </summary>
[Parameter]
public Color IconColor { get; set; } = Color.Default;
/// <summary>
/// The title of the directory selection dialog.
/// </summary>
[Parameter]
public string DirectoryDialogTitle { get; set; } = "Select Directory";
[Inject]
private RustService RustService { get; init; } = null!;
private string internalText = string.Empty;
private bool isDirectoryDialogOpen;
private readonly Timer timer = new(TimeSpan.FromMilliseconds(500))
{
AutoReset = false
};
#region Overrides of ConfigurationBase
/// <inheritdoc />
protected override bool Stretch => true;
protected override Variant Variant => Variant.Outlined;
protected override string Label => this.OptionDescription;
#endregion
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.timer.Elapsed += async (_, _) => await this.InvokeAsync(async () => await this.OptionChanged(this.internalText));
await base.OnInitializedAsync();
}
protected override async Task OnParametersSetAsync()
{
this.internalText = this.Text();
await base.OnParametersSetAsync();
}
#endregion
private void InternalUpdate(string text)
{
this.timer.Stop();
this.internalText = text;
this.timer.Start();
}
private async Task OpenDirectoryDialog()
{
if (this.isDirectoryDialogOpen)
return;
this.isDirectoryDialogOpen = true;
try
{
var response = await this.RustService.SelectDirectory(this.DirectoryDialogTitle, string.IsNullOrWhiteSpace(this.internalText) ? null : this.internalText);
if (response.UserCancelled)
return;
this.timer.Stop();
this.internalText = response.SelectedDirectory;
await this.OptionChanged(response.SelectedDirectory);
}
finally
{
this.isDirectoryDialogOpen = false;
}
}
private async Task OptionChanged(string updatedText)
{
this.TextUpdate(updatedText);
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
}
#region Overrides of MSGComponentBase
protected override void DisposeResources()
{
try
{
this.timer.Stop();
this.timer.Dispose();
}
catch
{
// ignore
}
base.DisposeResources();
}
#endregion
}
@@ -1,17 +1,46 @@
@inherits ConfigurationBaseCore
<MudTextField
T="string"
Text="@this.Text()"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="Adornment.Start"
AdornmentIcon="@this.Icon"
AdornmentColor="@this.IconColor"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Immediate="@true"
Underline="false"
/>
@if (this.ResetValue is null)
{
<MudTextField
T="string"
Text="@this.Text()"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="Adornment.Start"
AdornmentIcon="@this.Icon"
AdornmentColor="@this.IconColor"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Immediate="@true"
Validation="@this.Validation"
Underline="false"
/>
}
else
{
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudTextField
T="string"
Text="@this.Text()"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="Adornment.Start"
AdornmentIcon="@this.Icon"
AdornmentColor="@this.IconColor"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Immediate="@true"
Validation="@this.Validation"
Underline="false"
Class="flex-grow-1"
/>
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Restore" Disabled="@this.IsDisabled" OnClick="@this.ResetTextAsync" Class="mb-1">
@this.ResetButtonText
</MudButton>
</MudStack>
}
@@ -41,6 +41,24 @@ public partial class ConfigurationText : ConfigurationBaseCore
/// </summary>
[Parameter]
public int MaxLines { get; set; } = 12;
/// <summary>
/// When configured, displays a button which restores this value.
/// </summary>
[Parameter]
public Func<string>? ResetValue { get; set; }
/// <summary>
/// The text displayed on the optional reset button.
/// </summary>
[Parameter]
public string ResetButtonText { get; set; } = string.Empty;
/// <summary>
/// Validates the configured text before it is stored.
/// </summary>
[Parameter]
public Func<string, string?>? Validation { get; set; }
private string internalText = string.Empty;
private readonly Timer timer = new(TimeSpan.FromMilliseconds(500))
@@ -57,10 +75,6 @@ public partial class ConfigurationText : ConfigurationBaseCore
protected override string Label => this.OptionDescription;
#endregion
#region Overrides of ConfigurationBase
protected override async Task OnInitializedAsync()
{
this.timer.Elapsed += async (_, _) => await this.InvokeAsync(async () => await this.OptionChanged(this.internalText));
@@ -85,9 +99,22 @@ public partial class ConfigurationText : ConfigurationBaseCore
this.internalText = text;
this.timer.Start();
}
private async Task ResetTextAsync()
{
if (this.ResetValue is null || this.IsDisabled)
return;
this.timer.Stop();
this.internalText = this.ResetValue();
await this.OptionChanged(this.internalText);
}
private async Task OptionChanged(string updatedText)
{
if (this.Validation?.Invoke(updatedText) is not null)
return;
this.TextUpdate(updatedText);
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
@@ -27,6 +27,12 @@ public partial class ReadFileContent : MSGComponentBase
[Parameter]
public EventCallback<string> FileContentChanged { get; set; }
/// <summary>
/// Reports the path after a file was loaded successfully.
/// </summary>
[Parameter]
public EventCallback<string> FilePathLoaded { get; set; }
/// <summary>
/// If true, the component will display the state of the attached document (if any).
/// </summary>
@@ -50,6 +56,13 @@ public partial class ReadFileContent : MSGComponentBase
/// </summary>
[Parameter]
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]
private RustService RustService { get; init; } = null!;
@@ -252,7 +265,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 +323,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);
@@ -345,6 +365,7 @@ public partial class ReadFileContent : MSGComponentBase
private async Task ApplyFileContentAsync(string fileContent, string filePath)
{
await this.FileContentChanged.InvokeAsync(fileContent);
await this.FilePathLoaded.InvokeAsync(filePath);
this.loadedFileName = Path.GetFileName(filePath);
this.hasLoadedFileContent = true;
}
@@ -423,4 +444,4 @@ public partial class ReadFileContent : MSGComponentBase
this.ClearDragClass();
this.StateHasChanged();
}
}
}