2025-05-18 19:31:15 +00:00
using AIStudio.Components ;
2026-08-03 17:45:47 +00:00
using AIStudio.Provider ;
2025-01-13 18:51:26 +00:00
using AIStudio.Settings ;
using AIStudio.Settings.DataModel ;
using AIStudio.Tools.Validation ;
using Microsoft.AspNetCore.Components ;
namespace AIStudio.Dialogs ;
2025-05-18 19:31:15 +00:00
public partial class DataSourceLocalFileDialog : MSGComponentBase
2025-01-13 18:51:26 +00:00
{
[CascadingParameter]
2025-03-12 18:12:56 +00:00
private IMudDialogInstance MudDialog { get ; set ; } = null ! ;
2025-01-13 18:51:26 +00:00
[Parameter]
public bool IsEditing { get ; set ; }
[Parameter]
public DataSourceLocalFile DataSource { get ; set ; }
2026-08-03 15:53:31 +00:00
[Parameter]
public bool LockSourceAndEmbedding { get ; set ; }
2025-01-13 18:51:26 +00: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 19:53:46 +00:00
private string dataDescription = string . Empty ;
2025-01-13 18:51:26 +00:00
private bool dataUserAcknowledgedCloudEmbedding ;
private string dataEmbeddingId = string . Empty ;
private string dataFilePath = string . Empty ;
2026-07-29 16:47:59 +00:00
private int dataMaxChunkTokenLength ;
private int dataChunkOverlapTokenLength ;
2025-05-26 17:53:31 +00:00
private ushort dataMaxMatches = 10 ;
2026-07-29 16:47:59 +00:00
private bool showExpertSettings ;
2026-08-03 17:45:47 +00:00
private ConfidenceLevel dataComplianceLevel = ConfidenceLevel . UNKNOWN ;
2025-01-13 18:51:26 +00: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 13:05:17 +00:00
GetSelectedEmbeddingProvider = ( ) = > this . SelectedEmbedding ,
GetSecurityPolicy = ( ) = > DataSourceSecurity . ALLOW_ANY ,
GetComplianceLevel = ( ) = > this . dataComplianceLevel ,
GetSettingsManager = ( ) = > this . SettingsManager ,
2025-01-13 18:51:26 +00: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 19:53:46 +00:00
this . dataDescription = this . DataSource . Description ;
2025-01-13 18:51:26 +00:00
this . dataEmbeddingId = this . DataSource . EmbeddingId ;
this . dataFilePath = this . DataSource . FilePath ;
2026-07-29 16:47:59 +00:00
this . dataMaxChunkTokenLength = this . DataSource . MaxChunkTokenLength ;
this . dataChunkOverlapTokenLength = this . DataSource . ChunkOverlapTokenLength ;
2026-08-03 17:45:47 +00:00
this . dataComplianceLevel = this . DataSource . ComplianceLevel ;
2025-05-26 17:53:31 +00:00
this . dataMaxMatches = this . DataSource . MaxMatches ;
2026-07-29 16:47:59 +00:00
this . showExpertSettings = this . dataMaxChunkTokenLength > 0 | | this . dataChunkOverlapTokenLength > 0 ;
2025-01-13 18:51:26 +00: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 16:47:59 +00: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 18:51:26 +00:00
2026-08-03 15:53:31 +00:00
private bool CanChangeSourceAndEmbedding = > ! this . IsEditing | | ! this . LockSourceAndEmbedding ;
2026-08-03 17:45:47 +00:00
private IEnumerable < ConfigurationSelectData < ConfidenceLevel > > ComplianceLevels = > ConfigurationSelectDataFactory . GetDataSourceComplianceLevelsData ( ) ;
2026-08-04 16:01:25 +00: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 15:53:31 +00: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 18:51:26 +00:00
private DataSourceLocalFile CreateDataSource ( ) = > new ( )
{
Id = this . dataId ,
Num = this . dataNum ,
Name = this . dataName ,
2025-12-08 19:53:46 +00:00
Description = this . dataDescription ,
2025-01-13 18:51:26 +00:00
Type = DataSourceType . LOCAL_FILE ,
2026-08-03 15:53:31 +00:00
EmbeddingId = this . CanChangeSourceAndEmbedding ? this . dataEmbeddingId : this . DataSource . EmbeddingId ,
FilePath = this . CanChangeSourceAndEmbedding ? this . dataFilePath : this . DataSource . FilePath ,
2026-07-29 16:47:59 +00:00
MaxChunkTokenLength = this . dataMaxChunkTokenLength ,
ChunkOverlapTokenLength = this . dataChunkOverlapTokenLength ,
2026-08-05 13:05:17 +00:00
SecurityPolicy = DataSourceSecurity . ALLOW_ANY ,
2026-08-03 17:45:47 +00:00
ComplianceLevel = this . dataComplianceLevel ,
2025-05-26 17:53:31 +00:00
MaxMatches = this . dataMaxMatches ,
2025-01-13 18:51:26 +00: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 16:47:59 +00:00
private string? ValidateMaxChunkTokenLength ( int maxChunkTokenLength )
{
if ( maxChunkTokenLength < 0 )
2026-08-03 15:53:31 +00:00
return T ( "Please enter 0 or a positive token limit." ) ;
2026-07-29 16:47:59 +00:00
var providerMaxChunkTokenLength = this . SelectedEmbedding ? . EffectiveTokenLimit ? ? EmbeddingProvider . DEFAULT_TOKEN_LIMIT ;
2026-08-03 15:53:31 +00: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 16:47:59 +00: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 15:53:31 +00:00
return T ( "The overlap must be smaller than the effective token limit." ) ;
2026-07-29 16:47:59 +00:00
return null ;
}
private void ToggleExpertSettings ( ) = > this . showExpertSettings = ! this . showExpertSettings ;
private string GetExpertStyles = > this . showExpertSettings ? "border-2 border-dashed rounded pa-2" : string . Empty ;
}