Refactored configuration components to improve extensibility and uniformity

This commit is contained in:
Thorsten Sommer 2025-07-22 07:55:15 +02:00
parent 74449e40dd
commit e82d1e473f
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
19 changed files with 141 additions and 37 deletions

View File

@ -4,12 +4,20 @@
{
@if (!this.Disabled() && this.IsLocked())
{
<MudTooltip Text="@TB("This feature is managed by your organization and has therefore been disabled.")" Arrow="true" Placement="Placement.Top">
@this.Body
</MudTooltip>
<MudField Label="@this.Label" Variant="@this.Variant" Underline="false" HelperText="@this.OptionHelp" Class="@this.Classes" InnerPadding="false">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.FlexStart" Wrap="Wrap.NoWrap" StretchItems="this.StretchItems">
@* MudTooltip.RootStyle is set as a workaround for issue -> https://github.com/MudBlazor/MudBlazor/issues/10882 *@
<MudTooltip Text="@TB("This feature is managed by your organization and has therefore been disabled.")" Arrow="true" Placement="Placement.Right" RootStyle="display:inline-flex;">
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Error" Size="Size.Small" Class="mr-1"/>
</MudTooltip>
@this.Body
</MudStack>
</MudField>
}
else
{
@this.Body
<MudField Label="@this.Label" Variant="@this.Variant" Underline="false" HelperText="@this.OptionHelp" Class="@this.Classes" InnerPadding="false">
@this.Body
</MudField>
}
}

View File

@ -5,7 +5,7 @@ namespace AIStudio.Components;
/// <summary>
/// A base class for configuration options.
/// </summary>
public partial class ConfigurationBase : MSGComponentBase
public abstract partial class ConfigurationBase : MSGComponentBase
{
/// <summary>
/// The description of the option, i.e., the name. Should be
@ -31,12 +31,37 @@ public partial class ConfigurationBase : MSGComponentBase
/// </summary>
[Parameter]
public Func<bool> IsLocked { get; set; } = () => false;
/// <summary>
/// Should the option be stretched to fill the available space?
/// </summary>
protected abstract bool Stretch { get; }
/// <summary>
/// The CSS class to apply to the component.
/// </summary>
protected virtual string GetClassForBase => string.Empty;
/// <summary>
/// The visual variant of the option.
/// </summary>
protected virtual Variant Variant => Variant.Text;
/// <summary>
/// The label to display for the option.
/// </summary>
protected virtual string Label => string.Empty;
private StretchItems StretchItems => this.Stretch ? StretchItems.End : StretchItems.None;
protected bool IsDisabled => this.Disabled() || this.IsLocked();
private protected virtual RenderFragment? Body => null;
private string Classes => $"{this.GetClassForBase} {MARGIN_CLASS}";
private protected virtual RenderFragment? Body => null;
private const string MARGIN_CLASS = "mb-6";
protected const string MARGIN_CLASS = "mb-6";
protected static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
#region Overrides of ComponentBase

View File

@ -1,3 +1,3 @@
@using AIStudio.Settings
@inherits ConfigurationBaseCore
<ConfigurationSelect Disabled="@(() => this.IsDisabled)" OptionDescription="@T("Select a minimum confidence level")" SelectedValue="@this.FilteredSelectedValue" Data="@ConfigurationSelectDataFactory.GetConfidenceLevelsData(this.SettingsManager, this.RestrictToGlobalMinimumConfidence)" SelectionUpdate="@this.SelectionUpdate" OptionHelp="@T("Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level.")"/>
@inherits MSGComponentBase
<ConfigurationSelect IsLocked="this.IsLocked" Disabled="this.Disabled" OptionDescription="@T("Select a minimum confidence level")" SelectedValue="@this.FilteredSelectedValue" Data="@ConfigurationSelectDataFactory.GetConfidenceLevelsData(this.SettingsManager, this.RestrictToGlobalMinimumConfidence)" SelectionUpdate="@this.SelectionUpdate" OptionHelp="@T("Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level.")"/>

View File

@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class ConfigurationMinConfidenceSelection : ConfigurationBaseCore
public partial class ConfigurationMinConfidenceSelection : MSGComponentBase
{
/// <summary>
/// The selected value.
@ -23,6 +23,12 @@ public partial class ConfigurationMinConfidenceSelection : ConfigurationBaseCore
/// </summary>
[Parameter]
public bool RestrictToGlobalMinimumConfidence { get; set; }
[Parameter]
public Func<bool> Disabled { get; set; } = () => false;
[Parameter]
public Func<bool> IsLocked { get; set; } = () => false;
private ConfidenceLevel FilteredSelectedValue()
{

View File

@ -9,10 +9,8 @@
Strict="@true"
Disabled="@this.IsDisabled"
Margin="Margin.Dense"
Label="@this.OptionDescription"
Class="@GetClass"
Variant="Variant.Outlined"
HelperText="@this.OptionHelp"
Class="rounded-lg"
Underline="false"
SelectedValuesChanged="@this.OptionChanged">
@foreach (var data in this.Data)
{

View File

@ -28,6 +28,17 @@ public partial class ConfigurationMultiSelect<TData> : ConfigurationBaseCore
[Parameter]
public Action<HashSet<TData>> SelectionUpdate { get; set; } = _ => { };
#region Overrides of ConfigurationBase
/// <inheritdoc />
protected override bool Stretch => true;
protected override Variant Variant => Variant.Outlined;
protected override string Label => this.OptionDescription;
#endregion
private async Task OptionChanged(IEnumerable<TData?>? updatedValues)
{
if(updatedValues is null)
@ -39,8 +50,6 @@ public partial class ConfigurationMultiSelect<TData> : ConfigurationBaseCore
await this.InformAboutChange();
}
private static string GetClass => $"{MARGIN_CLASS} rounded-lg";
private string GetMultiSelectionText(List<TData?>? selectedValues)
{
if(selectedValues is null || selectedValues.Count == 0)

View File

@ -1,7 +1,5 @@
@inherits ConfigurationBaseCore
<MudField Disabled="@this.IsDisabled" Label="@this.OptionDescription" Variant="Variant.Outlined" HelperText="@this.OptionHelp" Class="@MARGIN_CLASS">
<MudSwitch T="bool" Disabled="@this.Disabled()" Value="@this.State()" ValueChanged="@this.OptionChanged" Color="Color.Primary">
@(this.State() ? this.LabelOn : this.LabelOff)
</MudSwitch>
</MudField>
<MudSwitch T="bool" Disabled="@this.IsDisabled" Value="@this.State()" ValueChanged="@this.OptionChanged" Color="Color.Primary">
@(this.State() ? this.LabelOn : this.LabelOff)
</MudSwitch>

View File

@ -31,6 +31,19 @@ public partial class ConfigurationOption : ConfigurationBaseCore
[Parameter]
public Action<bool> StateUpdate { get; set; } = _ => { };
#region Overrides of ConfigurationBase
/// <inheritdoc />
protected override bool Stretch => true;
/// <inheritdoc />
protected override Variant Variant => Variant.Outlined;
/// <inheritdoc />
protected override string Label => this.OptionDescription;
#endregion
private async Task OptionChanged(bool updatedState)
{
this.StateUpdate(updatedState);

View File

@ -1,2 +1,2 @@
@inherits ConfigurationBaseCore
<ConfigurationSelect OptionDescription="@T("Preselected provider")" Disabled="@(() => this.IsDisabled)" OptionHelp="@this.HelpText()" Data="@this.FilteredData()" SelectedValue="@this.SelectedValue" SelectionUpdate="@this.SelectionUpdate"/>
@inherits MSGComponentBase
<ConfigurationSelect IsLocked="@this.IsLocked" OptionDescription="@T("Preselected provider")" Disabled="@(() => this.Disabled())" OptionHelp="@this.HelpText()" Data="@this.FilteredData()" SelectedValue="@this.SelectedValue" SelectionUpdate="@this.SelectionUpdate"/>

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class ConfigurationProviderSelection : ConfigurationBaseCore
public partial class ConfigurationProviderSelection : MSGComponentBase
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationProviderSelection).Namespace, nameof(ConfigurationProviderSelection));
@ -26,6 +26,12 @@ public partial class ConfigurationProviderSelection : ConfigurationBaseCore
[Parameter]
public Tools.Components Component { get; set; } = Tools.Components.NONE;
[Parameter]
public Func<bool> Disabled { get; set; } = () => false;
[Parameter]
public Func<bool> IsLocked { get; set; } = () => false;
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
private IEnumerable<ConfigurationSelectData<string>> FilteredData()
{

View File

@ -1,7 +1,7 @@
@inherits ConfigurationBaseCore
@typeparam TConfig
<MudSelect T="TConfig" Value="@this.SelectedValue()" Strict="@true" ShrinkLabel="@true" Disabled="@this.IsDisabled" Margin="Margin.Dense" Label="@this.OptionDescription" Class="@GetClass" Variant="Variant.Outlined" HelperText="@this.OptionHelp" ValueChanged="@this.OptionChanged">
<MudSelect T="TConfig" Value="@this.SelectedValue()" Strict="@true" ShrinkLabel="@true" Disabled="@this.IsDisabled" Margin="Margin.Dense" Class="rounded-lg mb-0" Underline="false" ValueChanged="@this.OptionChanged">
@foreach (var data in this.Data)
{
<MudSelectItem Value="@data.Value">

View File

@ -28,12 +28,22 @@ public partial class ConfigurationSelect<TConfig> : ConfigurationBaseCore
[Parameter]
public Action<TConfig> SelectionUpdate { get; set; } = _ => { };
#region Overrides of ConfigurationBase
/// <inheritdoc />
protected override bool Stretch => true;
/// <inheritdoc />
protected override string Label => this.OptionDescription;
protected override Variant Variant => Variant.Outlined;
#endregion
private async Task OptionChanged(TConfig updatedValue)
{
this.SelectionUpdate(updatedValue);
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
}
private static string GetClass => $"{MARGIN_CLASS} rounded-lg";
}

View File

@ -1,8 +1,6 @@
@typeparam T
@inherits ConfigurationBaseCore
<MudField Label="@this.OptionDescription" Variant="Variant.Outlined" Class="mb-3" Disabled="@this.IsDisabled">
<MudSlider T="@T" Size="Size.Medium" Value="@this.Value()" ValueChanged="@this.OptionChanged" Min="@this.Min" Max="@this.Max" Step="@this.Step" Immediate="@true" Disabled="@this.IsDisabled">
@this.Value() @this.Unit
</MudSlider>
</MudField>
<MudSlider T="@T" Size="Size.Medium" Value="@this.Value()" ValueChanged="@this.OptionChanged" Min="@this.Min" Max="@this.Max" Step="@this.Step" Immediate="@true" Disabled="@this.IsDisabled" Class="mb-1">
@this.Value() @this.Unit
</MudSlider>

View File

@ -42,6 +42,18 @@ public partial class ConfigurationSlider<T> : ConfigurationBaseCore where T : st
[Parameter]
public Action<T> ValueUpdate { get; set; } = _ => { };
#region Overrides of ConfigurationBase
/// <inheritdoc />
protected override bool Stretch => true;
/// <inheritdoc />
protected override Variant Variant => Variant.Outlined;
protected override string Label => this.OptionDescription;
#endregion
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()

View File

@ -4,9 +4,7 @@
T="string"
Text="@this.Text()"
TextChanged="@this.InternalUpdate"
Label="@this.OptionDescription"
Disabled="@this.IsDisabled"
Class="@MARGIN_CLASS"
Adornment="Adornment.Start"
AdornmentIcon="@this.Icon"
AdornmentColor="@this.IconColor"
@ -15,5 +13,5 @@
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Immediate="@true"
Variant="Variant.Outlined"
Underline="false"
/>

View File

@ -47,6 +47,17 @@ public partial class ConfigurationText : ConfigurationBaseCore
{
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 ConfigurationBase

View File

@ -1,5 +1,5 @@
@inherits ConfigurationBaseCore
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@this.Icon" Disabled="@this.IsDisabled" Class="mt-3 mb-6" OnClick="@(async () => await this.ClickAsync())">
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@this.Icon" Disabled="@this.IsDisabled" OnClick="@(async () => await this.ClickAsync())">
@this.Text
</MudButton>

View File

@ -16,6 +16,18 @@ public partial class LockableButton : ConfigurationBaseCore
[Parameter]
public string Text { get; set; } = string.Empty;
[Parameter]
public string Class { get; set; } = string.Empty;
#region Overrides of ConfigurationBase
/// <inheritdoc />
protected override bool Stretch => false;
protected override string GetClassForBase => this.Class;
#endregion
private async Task ClickAsync()
{
if (this.IsLocked() || this.Disabled())

View File

@ -75,7 +75,7 @@
</MudText>
}
<LockableButton Text="@T("Add Provider")" IsLocked="@(() => !this.SettingsManager.ConfigurationData.App.AllowUserToAddProvider)" Icon="@Icons.Material.Filled.AddRoad" OnClickAsync="@this.AddLLMProvider" />
<LockableButton Text="@T("Add Provider")" IsLocked="@(() => !this.SettingsManager.ConfigurationData.App.AllowUserToAddProvider)" Icon="@Icons.Material.Filled.AddRoad" OnClickAsync="@this.AddLLMProvider" Class="mt-3" />
<MudText Typo="Typo.h4" Class="mb-3">
@T("LLM Provider Confidence")