Refactored data source validation

This commit is contained in:
Thorsten Sommer 2025-01-09 19:27:25 +01:00
parent 31db3f0931
commit 59c08fecee
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 140 additions and 87 deletions

View File

@ -10,7 +10,7 @@
MaxLength="40" MaxLength="40"
Counter="40" Counter="40"
Immediate="@true" Immediate="@true"
Validation="@this.ValidateName" Validation="@this.dataSourceValidation.ValidatingName"
Adornment="Adornment.Start" Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb" AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info" AdornmentColor="Color.Info"
@ -21,13 +21,13 @@
Select a root directory for this data source. All data in this directory and all Select a root directory for this data source. All data in this directory and all
its subdirectories will be processed for this data source. its subdirectories will be processed for this data source.
</MudJustifiedText> </MudJustifiedText>
<SelectDirectory @bind-Directory="@this.dataPath" Label="Selected base directory for this data source" DirectoryDialogTitle="Select the base directory" Validation="@this.ValidatePath" /> <SelectDirectory @bind-Directory="@this.dataPath" Label="Selected base directory for this data source" DirectoryDialogTitle="Select the base directory" Validation="@this.dataSourceValidation.ValidatePath" />
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
In order for the AI to be able to determine the appropriate data at any time, you must In order for the AI to be able to determine the appropriate data at any time, you must
choose an embedding method. choose an embedding method.
</MudJustifiedText> </MudJustifiedText>
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="Embedding" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidateEmbeddingId"> <MudSelect @bind-Value="@this.dataEmbeddingId" Label="Embedding" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId">
@foreach (var embedding in this.AvailableEmbeddings) @foreach (var embedding in this.AvailableEmbeddings)
{ {
<MudSelectItem Value="@embedding.Value">@embedding.Name</MudSelectItem> <MudSelectItem Value="@embedding.Value">@embedding.Name</MudSelectItem>
@ -51,7 +51,7 @@
@: confirm that you have read and understood this. @: confirm that you have read and understood this.
} }
</MudJustifiedText> </MudJustifiedText>
<MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="I confirm that I have read and understood the above" LabelOn="Yes, please send my data to the cloud" LabelOff="No, I will chose another embedding" Validation="@this.ValidateUserAcknowledgedCloudEmbedding"/> <MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="I confirm that I have read and understood the above" LabelOn="Yes, please send my data to the cloud" LabelOff="No, I will chose another embedding" Validation="@this.dataSourceValidation.ValidateUserAcknowledgedCloudEmbedding"/>
} }
else else
{ {

View File

@ -1,5 +1,6 @@
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Settings.DataModel; using AIStudio.Settings.DataModel;
using AIStudio.Tools.Validation;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -24,6 +25,8 @@ public partial class DataSourceLocalDirectoryDialog : ComponentBase
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new(); private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private readonly DataSourceValidation dataSourceValidation;
/// <summary> /// <summary>
/// The list of used data source names. We need this to check for uniqueness. /// The list of used data source names. We need this to check for uniqueness.
/// </summary> /// </summary>
@ -42,7 +45,17 @@ public partial class DataSourceLocalDirectoryDialog : ComponentBase
// We get the form reference from Blazor code to validate it manually: // We get the form reference from Blazor code to validate it manually:
private MudForm form = null!; private MudForm form = null!;
public DataSourceLocalDirectoryDialog()
{
this.dataSourceValidation = new()
{
GetSelectedCloudEmbedding = () => this.SelectedCloudEmbedding,
GetPreviousDataSourceName = () => this.dataEditingPreviousInstanceName,
GetUsedDataSourceNames = () => this.UsedDataSourcesNames,
};
}
#region Overrides of ComponentBase #region Overrides of ComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@ -79,45 +92,6 @@ public partial class DataSourceLocalDirectoryDialog : ComponentBase
#endregion #endregion
private string? ValidateName(string value)
{
if(string.IsNullOrWhiteSpace(value))
return "The name must not be empty.";
if (value.Length > 40)
return "The name must not exceed 40 characters.";
var lowerName = value.ToLowerInvariant();
if(lowerName != this.dataEditingPreviousInstanceName && this.UsedDataSourcesNames.Contains(lowerName))
return "The name is already used by another data source. Please choose a different name.";
return null;
}
private string? ValidatePath(string value)
{
if(string.IsNullOrWhiteSpace(value))
return "The path must not be empty. Please select a directory.";
return null;
}
private string? ValidateEmbeddingId(string value)
{
if(string.IsNullOrWhiteSpace(value))
return "Please select an embedding provider.";
return null;
}
private string? ValidateUserAcknowledgedCloudEmbedding(bool value)
{
if(this.SelectedCloudEmbedding && !value)
return "Please acknowledge that you are aware of the cloud embedding implications.";
return null;
}
private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted; private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted;
private DataSourceLocalDirectory CreateDataSource() => new() private DataSourceLocalDirectory CreateDataSource() => new()

View File

@ -10,7 +10,7 @@
MaxLength="40" MaxLength="40"
Counter="40" Counter="40"
Immediate="@true" Immediate="@true"
Validation="@this.ValidateName" Validation="@this.dataSourceValidation.ValidatingName"
Adornment="Adornment.Start" Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb" AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info" AdornmentColor="Color.Info"
@ -20,13 +20,13 @@
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
Select a file for this data source. The content of this file will be processed for the data source. Select a file for this data source. The content of this file will be processed for the data source.
</MudJustifiedText> </MudJustifiedText>
<SelectFile @bind-File="@this.dataFilePath" Label="Selected file path for this data source" FileDialogTitle="Select the file" Validation="@this.ValidateFilePath" /> <SelectFile @bind-File="@this.dataFilePath" Label="Selected file path for this data source" FileDialogTitle="Select the file" Validation="@this.dataSourceValidation.ValidateFilePath" />
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
In order for the AI to be able to determine the appropriate data at any time, you must In order for the AI to be able to determine the appropriate data at any time, you must
choose an embedding method. choose an embedding method.
</MudJustifiedText> </MudJustifiedText>
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="Embedding" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidateEmbeddingId"> <MudSelect @bind-Value="@this.dataEmbeddingId" Label="Embedding" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.GetSelectedCloudEmbedding">
@foreach (var embedding in this.AvailableEmbeddings) @foreach (var embedding in this.AvailableEmbeddings)
{ {
<MudSelectItem Value="@embedding.Value">@embedding.Name</MudSelectItem> <MudSelectItem Value="@embedding.Value">@embedding.Name</MudSelectItem>
@ -50,7 +50,7 @@
@: and understood this. @: and understood this.
} }
</MudJustifiedText> </MudJustifiedText>
<MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="I confirm that I have read and understood the above" LabelOn="Yes, please send my data to the cloud" LabelOff="No, I will chose another embedding" Validation="@this.ValidateUserAcknowledgedCloudEmbedding"/> <MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="I confirm that I have read and understood the above" LabelOn="Yes, please send my data to the cloud" LabelOff="No, I will chose another embedding" Validation="@this.dataSourceValidation.ValidateUserAcknowledgedCloudEmbedding"/>
} }
else else
{ {

View File

@ -1,5 +1,6 @@
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Settings.DataModel; using AIStudio.Settings.DataModel;
using AIStudio.Tools.Validation;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -25,6 +26,8 @@ public partial class DataSourceLocalFileDialog : ComponentBase
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new(); private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private readonly DataSourceValidation dataSourceValidation;
/// <summary> /// <summary>
/// The list of used data source names. We need this to check for uniqueness. /// The list of used data source names. We need this to check for uniqueness.
/// </summary> /// </summary>
@ -43,6 +46,16 @@ public partial class DataSourceLocalFileDialog : ComponentBase
// We get the form reference from Blazor code to validate it manually: // We get the form reference from Blazor code to validate it manually:
private MudForm form = null!; private MudForm form = null!;
public DataSourceLocalFileDialog()
{
this.dataSourceValidation = new()
{
GetSelectedCloudEmbedding = () => this.SelectedCloudEmbedding,
GetPreviousDataSourceName = () => this.dataEditingPreviousInstanceName,
GetUsedDataSourceNames = () => this.UsedDataSourcesNames,
};
}
#region Overrides of ComponentBase #region Overrides of ComponentBase
@ -80,45 +93,6 @@ public partial class DataSourceLocalFileDialog : ComponentBase
#endregion #endregion
private string? ValidateName(string value)
{
if(string.IsNullOrWhiteSpace(value))
return "The name must not be empty.";
if (value.Length > 40)
return "The name must not exceed 40 characters.";
var lowerName = value.ToLowerInvariant();
if(lowerName != this.dataEditingPreviousInstanceName && this.UsedDataSourcesNames.Contains(lowerName))
return "The name is already used by another data source. Please choose a different name.";
return null;
}
private string? ValidateFilePath(string value)
{
if(string.IsNullOrWhiteSpace(value))
return "The file path must not be empty. Please select a file.";
return null;
}
private string? ValidateEmbeddingId(string value)
{
if(string.IsNullOrWhiteSpace(value))
return "Please select an embedding provider.";
return null;
}
private string? ValidateUserAcknowledgedCloudEmbedding(bool value)
{
if(this.SelectedCloudEmbedding && !value)
return "Please acknowledge that you are aware of the cloud embedding implications.";
return null;
}
private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted; private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted;
private DataSourceLocalFile CreateDataSource() => new() private DataSourceLocalFile CreateDataSource() => new()

View File

@ -0,0 +1,105 @@
using ERI_Client.V1;
namespace AIStudio.Tools.Validation;
public sealed class DataSourceValidation
{
public Func<string> GetSecretStorageIssue { get; init; } = () => string.Empty;
public Func<string> GetPreviousDataSourceName { get; init; } = () => string.Empty;
public Func<IEnumerable<string>> GetUsedDataSourceNames { get; init; } = () => [];
public Func<AuthMethod> GetAuthMethod { get; init; } = () => AuthMethod.NONE;
public Func<bool> GetSelectedCloudEmbedding { get; init; } = () => false;
public string? ValidatingHostname(string hostname)
{
if(string.IsNullOrWhiteSpace(hostname))
return "Please enter a hostname, e.g., http://localhost:1234";
if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
return "The hostname must start with either http:// or https://";
if(!Uri.TryCreate(hostname, UriKind.Absolute, out _))
return "The hostname is not a valid HTTP(S) URL.";
return null;
}
public string? ValidatingSecret(string secret)
{
var authMethod = this.GetAuthMethod();
if(authMethod is AuthMethod.NONE or AuthMethod.KERBEROS)
return null;
var secretStorageIssue = this.GetSecretStorageIssue();
if(!string.IsNullOrWhiteSpace(secretStorageIssue))
return secretStorageIssue;
if (string.IsNullOrWhiteSpace(secret))
return authMethod switch
{
AuthMethod.TOKEN => "Please enter your secure token.",
AuthMethod.USERNAME_PASSWORD => "Please enter your password.",
_ => "Please enter the secret necessary for authentication."
};
return null;
}
public string? ValidatingName(string dataSourceName)
{
if(string.IsNullOrWhiteSpace(dataSourceName))
return "The name must not be empty.";
if (dataSourceName.Length > 40)
return "The name must not exceed 40 characters.";
var lowerName = dataSourceName.ToLowerInvariant();
if(lowerName != this.GetPreviousDataSourceName() && this.GetUsedDataSourceNames().Contains(lowerName))
return "The name is already used by another data source. Please choose a different name.";
return null;
}
public string? ValidatePath(string path)
{
if(string.IsNullOrWhiteSpace(path))
return "The path must not be empty. Please select a directory.";
if(!Directory.Exists(path))
return "The path does not exist. Please select a valid directory.";
return null;
}
public string? ValidateFilePath(string filePath)
{
if(string.IsNullOrWhiteSpace(filePath))
return "The file path must not be empty. Please select a file.";
if(!File.Exists(filePath))
return "The file does not exist. Please select a valid file.";
return null;
}
public string? ValidateEmbeddingId(string embeddingId)
{
if(string.IsNullOrWhiteSpace(embeddingId))
return "Please select an embedding provider.";
return null;
}
public string? ValidateUserAcknowledgedCloudEmbedding(bool value)
{
if(this.GetSelectedCloudEmbedding() && !value)
return "Please acknowledge that you are aware of the cloud embedding implications.";
return null;
}
}