2025-05-18 21:31:15 +02:00
using AIStudio.Components ;
2026-08-03 19:45:47 +02:00
using AIStudio.Provider ;
2025-01-13 19:51:26 +01:00
using AIStudio.Settings ;
using AIStudio.Settings.DataModel ;
using AIStudio.Tools.Validation ;
using Microsoft.AspNetCore.Components ;
namespace AIStudio.Dialogs ;
2025-05-18 21:31:15 +02:00
public partial class DataSourceLocalFileDialog : MSGComponentBase
2025-01-13 19:51:26 +01:00
{
[CascadingParameter]
2025-03-12 19:12:56 +01:00
private IMudDialogInstance MudDialog { get ; set ; } = null !;
2025-01-13 19:51:26 +01:00
[Parameter]
public bool IsEditing { get ; set ; }
[Parameter]
public DataSourceLocalFile DataSource { get ; set ; }
2026-08-03 17:53:31 +02:00
[Parameter]
public bool LockSourceAndEmbedding { get ; set ; }
2025-01-13 19:51:26 +01:00
[Parameter]
public IReadOnlyList < ConfigurationSelectData < string >> AvailableEmbeddings { get ; set ; } = [];
private static readonly Dictionary < string , object? > SPELLCHECK_ATTRIBUTES = new ();
private readonly DataSourceValidation dataSourceValidation ;
/// <summary>
/// The list of used data source names. We need this to check for uniqueness.
/// </summary>
private List < string > UsedDataSourcesNames { get ; set ; } = [];
private bool dataIsValid ;
private string [] dataIssues = [];
private string dataEditingPreviousInstanceName = string . Empty ;
private uint dataNum ;
private string dataId = Guid . NewGuid (). ToString ();
private string dataName = string . Empty ;
2025-12-08 20:53:46 +01:00
private string dataDescription = string . Empty ;
2025-01-13 19:51:26 +01:00
private bool dataUserAcknowledgedCloudEmbedding ;
private string dataEmbeddingId = string . Empty ;
private string dataFilePath = string . Empty ;
2026-07-29 18:47:59 +02:00
private int dataMaxChunkTokenLength ;
private int dataChunkOverlapTokenLength ;
2025-05-26 19:53:31 +02:00
private ushort dataMaxMatches = 10 ;
2026-07-29 18:47:59 +02:00
private bool showExpertSettings ;
2026-08-03 19:45:47 +02:00
private ConfidenceLevel dataComplianceLevel = ConfidenceLevel . UNKNOWN ;
2025-01-13 19:51:26 +01:00
// We get the form reference from Blazor code to validate it manually:
private MudForm form = null !;
public DataSourceLocalFileDialog ()
{
this . dataSourceValidation = new ()
{
GetSelectedCloudEmbedding = () => this . SelectedCloudEmbedding ,
2026-08-05 15:05:17 +02:00
GetSelectedEmbeddingProvider = () => this . SelectedEmbedding ,
GetSecurityPolicy = () => DataSourceSecurity . ALLOW_ANY ,
GetComplianceLevel = () => this . dataComplianceLevel ,
GetSettingsManager = () => this . SettingsManager ,
2025-01-13 19:51:26 +01:00
GetPreviousDataSourceName = () => this . dataEditingPreviousInstanceName ,
GetUsedDataSourceNames = () => this . UsedDataSourcesNames ,
};
}
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync ()
{
// Configure the spellchecking for the instance name input:
this . SettingsManager . InjectSpellchecking ( SPELLCHECK_ATTRIBUTES );
// Load the used instance names:
this . UsedDataSourcesNames = this . SettingsManager . ConfigurationData . DataSources . Select ( x => x . Name . ToLowerInvariant ()). ToList ();
// When editing, we need to load the data:
if ( this . IsEditing )
{
this . dataEditingPreviousInstanceName = this . DataSource . Name . ToLowerInvariant ();
this . dataNum = this . DataSource . Num ;
this . dataId = this . DataSource . Id ;
this . dataName = this . DataSource . Name ;
2025-12-08 20:53:46 +01:00
this . dataDescription = this . DataSource . Description ;
2025-01-13 19:51:26 +01:00
this . dataEmbeddingId = this . DataSource . EmbeddingId ;
this . dataFilePath = this . DataSource . FilePath ;
2026-07-29 18:47:59 +02:00
this . dataMaxChunkTokenLength = this . DataSource . MaxChunkTokenLength ;
this . dataChunkOverlapTokenLength = this . DataSource . ChunkOverlapTokenLength ;
2026-08-03 19:45:47 +02:00
this . dataComplianceLevel = this . DataSource . ComplianceLevel ;
2025-05-26 19:53:31 +02:00
this . dataMaxMatches = this . DataSource . MaxMatches ;
2026-07-29 18:47:59 +02:00
this . showExpertSettings = this . dataMaxChunkTokenLength > 0 || this . dataChunkOverlapTokenLength > 0 ;
2025-01-13 19:51:26 +01:00
}
await base . OnInitializedAsync ();
}
protected override async Task OnAfterRenderAsync ( bool firstRender )
{
// Reset the validation when not editing and on the first render.
// We don't want to show validation errors when the user opens the dialog.
if (! this . IsEditing && firstRender )
this . form . ResetValidation ();
await base . OnAfterRenderAsync ( firstRender );
}
#endregion
2026-07-29 18:47:59 +02:00
private EmbeddingProvider ? SelectedEmbedding => this . SettingsManager . ConfigurationData . EmbeddingProviders
. FirstOrDefault ( x => x . Id == this . dataEmbeddingId );
private bool SelectedCloudEmbedding => this . SelectedEmbedding is { IsSelfHosted : false };
2025-01-13 19:51:26 +01:00
2026-08-03 17:53:31 +02:00
private bool CanChangeSourceAndEmbedding => ! this . IsEditing || ! this . LockSourceAndEmbedding ;
2026-08-03 19:45:47 +02:00
private IEnumerable < ConfigurationSelectData < ConfidenceLevel >> ComplianceLevels => ConfigurationSelectDataFactory . GetDataSourceComplianceLevelsData ();
2026-08-04 18:01:25 +02:00
private string SelectedEmbeddingNameText
{
get
{
var selectedEmbedding = this . AvailableEmbeddings . FirstOrDefault ( x => x . Value == this . dataEmbeddingId );
return string . IsNullOrWhiteSpace ( selectedEmbedding . Name ) ? T ( "Unknown" ) : selectedEmbedding . Name ;
}
}
2026-08-03 17:53:31 +02:00
private string SelectedEmbeddingTokenizerText => this . SelectedEmbedding is null
? T ( "No embedding selected" )
: string . IsNullOrWhiteSpace ( this . SelectedEmbedding . TokenizerPath )
? T ( "Default tokenizer" )
: System . IO . Path . GetFileName ( this . SelectedEmbedding . TokenizerPath );
2025-01-13 19:51:26 +01:00
private DataSourceLocalFile CreateDataSource () => new ()
{
Id = this . dataId ,
Num = this . dataNum ,
Name = this . dataName ,
2025-12-08 20:53:46 +01:00
Description = this . dataDescription ,
2025-01-13 19:51:26 +01:00
Type = DataSourceType . LOCAL_FILE ,
2026-08-03 17:53:31 +02:00
EmbeddingId = this . CanChangeSourceAndEmbedding ? this . dataEmbeddingId : this . DataSource . EmbeddingId ,
FilePath = this . CanChangeSourceAndEmbedding ? this . dataFilePath : this . DataSource . FilePath ,
2026-07-29 18:47:59 +02:00
MaxChunkTokenLength = this . dataMaxChunkTokenLength ,
ChunkOverlapTokenLength = this . dataChunkOverlapTokenLength ,
2026-08-05 15:05:17 +02:00
SecurityPolicy = DataSourceSecurity . ALLOW_ANY ,
2026-08-03 19:45:47 +02:00
ComplianceLevel = this . dataComplianceLevel ,
2025-05-26 19:53:31 +02:00
MaxMatches = this . dataMaxMatches ,
2025-01-13 19:51:26 +01:00
};
private async Task Store ()
{
await this . form . Validate ();
// When the data is not valid, we don't store it:
if (! this . dataIsValid )
return ;
var addedDataSource = this . CreateDataSource ();
this . MudDialog . Close ( DialogResult . Ok ( addedDataSource ));
}
private void Cancel () => this . MudDialog . Cancel ();
2026-07-29 18:47:59 +02:00
private string? ValidateMaxChunkTokenLength ( int maxChunkTokenLength )
{
if ( maxChunkTokenLength < 0 )
2026-08-03 17:53:31 +02:00
return T ( "Please enter 0 or a positive token limit." );
2026-07-29 18:47:59 +02:00
var providerMaxChunkTokenLength = this . SelectedEmbedding ?. EffectiveTokenLimit ?? EmbeddingProvider . DEFAULT_TOKEN_LIMIT ;
2026-08-03 17:53:31 +02:00
if ( maxChunkTokenLength > 0 && maxChunkTokenLength >= providerMaxChunkTokenLength )
return string . Format ( T ( "The data source token limit must be smaller than the embedding provider token limit ({0}). Use 0 to use the provider setting." ), providerMaxChunkTokenLength );
2026-07-29 18:47:59 +02:00
return null ;
}
private string? ValidateChunkOverlapTokenLength ( int chunkOverlapTokenLength )
{
if ( chunkOverlapTokenLength < 0 )
return T ( "Please enter 0 or a positive overlap length." );
var effectiveMaxChunkTokenLength = this . dataMaxChunkTokenLength > 0
? this . dataMaxChunkTokenLength
: this . SelectedEmbedding ?. EffectiveTokenLimit ?? EmbeddingProvider . DEFAULT_TOKEN_LIMIT ;
if ( chunkOverlapTokenLength >= effectiveMaxChunkTokenLength )
2026-08-03 17:53:31 +02:00
return T ( "The overlap must be smaller than the effective token limit." );
2026-07-29 18:47:59 +02:00
return null ;
}
private void ToggleExpertSettings () => this . showExpertSettings = ! this . showExpertSettings ;
private string GetExpertStyles => this . showExpertSettings ? "border-2 border-dashed rounded pa-2" : string . Empty ;
}