diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor index 327350bb..22d1c984 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor +++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor @@ -10,7 +10,7 @@ MaxLength="40" Counter="40" Immediate="@true" - Validation="@this.ValidateName" + Validation="@this.dataSourceValidation.ValidatingName" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Lightbulb" AdornmentColor="Color.Info" @@ -21,13 +21,13 @@ Select a root directory for this data source. All data in this directory and all its subdirectories will be processed for this data source. - + In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method. - + @foreach (var embedding in this.AvailableEmbeddings) { @embedding.Name @@ -51,7 +51,7 @@ @: confirm that you have read and understood this. } - + } else { diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs index 2210e908..9f1b4f1f 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs @@ -1,5 +1,6 @@ using AIStudio.Settings; using AIStudio.Settings.DataModel; +using AIStudio.Tools.Validation; using Microsoft.AspNetCore.Components; @@ -24,6 +25,8 @@ public partial class DataSourceLocalDirectoryDialog : ComponentBase private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); + private readonly DataSourceValidation dataSourceValidation; + /// /// The list of used data source names. We need this to check for uniqueness. /// @@ -42,7 +45,17 @@ public partial class DataSourceLocalDirectoryDialog : ComponentBase // We get the form reference from Blazor code to validate it manually: private MudForm form = null!; - + + public DataSourceLocalDirectoryDialog() + { + this.dataSourceValidation = new() + { + GetSelectedCloudEmbedding = () => this.SelectedCloudEmbedding, + GetPreviousDataSourceName = () => this.dataEditingPreviousInstanceName, + GetUsedDataSourceNames = () => this.UsedDataSourcesNames, + }; + } + #region Overrides of ComponentBase protected override async Task OnInitializedAsync() @@ -79,45 +92,6 @@ public partial class DataSourceLocalDirectoryDialog : ComponentBase #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 DataSourceLocalDirectory CreateDataSource() => new() diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor index edc56f60..c60d32dc 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor +++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor @@ -10,7 +10,7 @@ MaxLength="40" Counter="40" Immediate="@true" - Validation="@this.ValidateName" + Validation="@this.dataSourceValidation.ValidatingName" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Lightbulb" AdornmentColor="Color.Info" @@ -20,13 +20,13 @@ Select a file for this data source. The content of this file will be processed for the data source. - + In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method. - + @foreach (var embedding in this.AvailableEmbeddings) { @embedding.Name @@ -50,7 +50,7 @@ @: and understood this. } - + } else { diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs index 4edea086..feaf5a79 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs @@ -1,5 +1,6 @@ using AIStudio.Settings; using AIStudio.Settings.DataModel; +using AIStudio.Tools.Validation; using Microsoft.AspNetCore.Components; @@ -25,6 +26,8 @@ public partial class DataSourceLocalFileDialog : ComponentBase private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); + private readonly DataSourceValidation dataSourceValidation; + /// /// The list of used data source names. We need this to check for uniqueness. /// @@ -43,6 +46,16 @@ public partial class DataSourceLocalFileDialog : ComponentBase // We get the form reference from Blazor code to validate it manually: private MudForm form = null!; + + public DataSourceLocalFileDialog() + { + this.dataSourceValidation = new() + { + GetSelectedCloudEmbedding = () => this.SelectedCloudEmbedding, + GetPreviousDataSourceName = () => this.dataEditingPreviousInstanceName, + GetUsedDataSourceNames = () => this.UsedDataSourcesNames, + }; + } #region Overrides of ComponentBase @@ -80,45 +93,6 @@ public partial class DataSourceLocalFileDialog : ComponentBase #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 DataSourceLocalFile CreateDataSource() => new() diff --git a/app/MindWork AI Studio/Tools/Validation/DataSourceValidation.cs b/app/MindWork AI Studio/Tools/Validation/DataSourceValidation.cs new file mode 100644 index 00000000..1fdd359a --- /dev/null +++ b/app/MindWork AI Studio/Tools/Validation/DataSourceValidation.cs @@ -0,0 +1,105 @@ +using ERI_Client.V1; + +namespace AIStudio.Tools.Validation; + +public sealed class DataSourceValidation +{ + public Func GetSecretStorageIssue { get; init; } = () => string.Empty; + + public Func GetPreviousDataSourceName { get; init; } = () => string.Empty; + + public Func> GetUsedDataSourceNames { get; init; } = () => []; + + public Func GetAuthMethod { get; init; } = () => AuthMethod.NONE; + + public Func 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; + } +} \ No newline at end of file