2025-01-13 19:51:26 +01:00
using AIStudio.Settings ;
using AIStudio.Settings.DataModel ;
2025-02-09 12:36:37 +01:00
using AIStudio.Tools.ERIClient.DataModel ;
2026-05-18 16:26:51 +02:00
using AIStudio.Tools.PluginSystem ;
2026-05-27 20:02:43 +02:00
using AIStudio.Tools.Services ;
using Microsoft.AspNetCore.Components ;
2025-01-13 19:51:26 +01:00
2025-03-16 14:03:43 +01:00
namespace AIStudio.Dialogs.Settings ;
2025-01-13 19:51:26 +01:00
2025-03-16 14:03:43 +01:00
public partial class SettingsDialogDataSources : SettingsDialogBase
2025-01-13 19:51:26 +01:00
{
2026-05-08 17:37:34 +02:00
[Inject]
private DataSourceEmbeddingService DataSourceEmbeddingService { get ; init ; } = null !;
2026-08-13 15:51:28 +02:00
protected override async Task OnInitializedAsync ()
{
await base . OnInitializedAsync ();
this . ApplyFilters ([], [ Event . CONFIGURATION_CHANGED , Event . RAG_EMBEDDING_STATUS_CHANGED ]);
}
protected override async Task ProcessIncomingMessage < T >( ComponentBase ? sendingComponent , Event triggeredEvent , T ? data ) where T : default
{
await base . ProcessIncomingMessage ( sendingComponent , triggeredEvent , data );
if ( triggeredEvent is Event . RAG_EMBEDDING_STATUS_CHANGED )
this . StateHasChanged ();
}
private static Color GetIndexingStatusColor ( DataSourceEmbeddingStatus ? status )
{
if ( status is null || status . State is DataSourceEmbeddingState . IDLE or DataSourceEmbeddingState . QUEUED or DataSourceEmbeddingState . RUNNING )
return Color . Warning ;
return status . State is DataSourceEmbeddingState . FAILED || status . FailedFiles > 0
? Color . Error
: Color . Success ;
}
2025-01-13 19:51:26 +01:00
private string GetEmbeddingName ( IDataSource dataSource )
{
if ( dataSource is IInternalDataSource internalDataSource )
{
var matchedEmbedding = this . SettingsManager . ConfigurationData . EmbeddingProviders . FirstOrDefault ( x => x . Id == internalDataSource . EmbeddingId );
if ( matchedEmbedding == default )
2025-04-27 16:13:15 +02:00
return T ( "No valid embedding" );
2025-01-13 19:51:26 +01:00
return matchedEmbedding . Name ;
}
if ( dataSource is IExternalDataSource )
2025-04-27 16:13:15 +02:00
return T ( "External (ERI)" );
2025-01-13 19:51:26 +01:00
2025-04-24 13:50:14 +02:00
return T ( "Unknown" );
2025-01-13 19:51:26 +01:00
}
2026-05-13 18:13:34 +02:00
private bool CanRefreshDataSource ( IDataSource dataSource )
{
2026-07-28 20:22:37 +02:00
return this . DataSourceEmbeddingService . CanRefreshDataSource ( dataSource );
2026-05-13 18:13:34 +02:00
}
private bool HasRefreshableDataSources ()
{
return this . SettingsManager . ConfigurationData . DataSources . Any ( this . CanRefreshDataSource );
}
private async Task AutomaticRefreshChanged ( bool enabled )
{
2026-08-10 23:12:55 +02:00
this . SettingsManager . ConfigurationData . App . DataSourceIndexing . AutomaticRefresh = enabled ;
2026-05-13 18:13:34 +02:00
await this . SettingsManager . StoreSettings ();
this . DataSourceEmbeddingService . RefreshAutomaticWatchers ();
await this . MessageBus . SendMessage < bool >( this , Event . CONFIGURATION_CHANGED );
}
private async Task RefreshAllDataSources ()
{
await this . DataSourceEmbeddingService . QueueAllInternalDataSourcesAsync ();
await this . MessageBus . SendMessage < bool >( this , Event . CONFIGURATION_CHANGED );
}
private async Task RefreshDataSource ( IDataSource dataSource )
{
if (! this . CanRefreshDataSource ( dataSource ))
return ;
await this . DataSourceEmbeddingService . QueueDataSourceAsync ( dataSource );
await this . MessageBus . SendMessage < bool >( this , Event . CONFIGURATION_CHANGED );
}
2025-01-13 19:51:26 +01:00
private async Task AddDataSource ( DataSourceType type )
{
IDataSource ? addedDataSource = null ;
switch ( type )
{
case DataSourceType . LOCAL_FILE :
var localFileDialogParameters = new DialogParameters < DataSourceLocalFileDialog >
{
{ x => x . IsEditing , false },
2026-04-16 16:48:47 +02:00
{ x => x . AvailableEmbeddings , this . AvailableEmbeddingProviders }
2025-01-13 19:51:26 +01:00
};
2025-04-24 13:50:14 +02:00
var localFileDialogReference = await this . DialogService . ShowAsync < DataSourceLocalFileDialog >( T ( "Add Local File as Data Source" ), localFileDialogParameters , DialogOptions . FULLSCREEN );
2025-01-13 19:51:26 +01:00
var localFileDialogResult = await localFileDialogReference . Result ;
if ( localFileDialogResult is null || localFileDialogResult . Canceled )
return ;
var localFile = ( DataSourceLocalFile ) localFileDialogResult . Data !;
localFile = localFile with { Num = this . SettingsManager . ConfigurationData . NextDataSourceNum ++ };
addedDataSource = localFile ;
break ;
case DataSourceType . LOCAL_DIRECTORY :
var localDirectoryDialogParameters = new DialogParameters < DataSourceLocalDirectoryDialog >
{
{ x => x . IsEditing , false },
2026-04-16 16:48:47 +02:00
{ x => x . AvailableEmbeddings , this . AvailableEmbeddingProviders }
2025-01-13 19:51:26 +01:00
};
2025-04-24 13:50:14 +02:00
var localDirectoryDialogReference = await this . DialogService . ShowAsync < DataSourceLocalDirectoryDialog >( T ( "Add Local Directory as Data Source" ), localDirectoryDialogParameters , DialogOptions . FULLSCREEN );
2025-01-13 19:51:26 +01:00
var localDirectoryDialogResult = await localDirectoryDialogReference . Result ;
if ( localDirectoryDialogResult is null || localDirectoryDialogResult . Canceled )
return ;
var localDirectory = ( DataSourceLocalDirectory ) localDirectoryDialogResult . Data !;
localDirectory = localDirectory with { Num = this . SettingsManager . ConfigurationData . NextDataSourceNum ++ };
addedDataSource = localDirectory ;
break ;
case DataSourceType . ERI_V1 :
var eriDialogParameters = new DialogParameters < DataSourceERI_V1Dialog >
{
{ x => x . IsEditing , false },
};
2025-04-24 13:50:14 +02:00
var eriDialogReference = await this . DialogService . ShowAsync < DataSourceERI_V1Dialog >( T ( "Add ERI v1 Data Source" ), eriDialogParameters , DialogOptions . FULLSCREEN );
2025-01-13 19:51:26 +01:00
var eriDialogResult = await eriDialogReference . Result ;
if ( eriDialogResult is null || eriDialogResult . Canceled )
return ;
var eriDataSource = ( DataSourceERI_V1 ) eriDialogResult . Data !;
eriDataSource = eriDataSource with { Num = this . SettingsManager . ConfigurationData . NextDataSourceNum ++ };
addedDataSource = eriDataSource ;
break ;
}
if ( addedDataSource is null )
return ;
this . SettingsManager . ConfigurationData . DataSources . Add ( addedDataSource );
await this . SettingsManager . StoreSettings ();
2026-05-08 17:37:34 +02:00
await this . DataSourceEmbeddingService . QueueDataSourceAsync ( addedDataSource );
2025-01-13 19:51:26 +01:00
await this . MessageBus . SendMessage < bool >( this , Event . CONFIGURATION_CHANGED );
}
2026-05-18 16:26:51 +02:00
private async Task ExportDataSource ( IDataSource dataSource )
{
if (! this . SettingsManager . ConfigurationData . App . ShowAdminSettings )
return ;
if ( dataSource is not DataSourceERI_V1 eriDataSource )
return ;
if ( eriDataSource . AuthMethod is AuthMethod . KERBEROS )
{
await this . DialogService . ShowMessageBox (
T ( "Export ERI Data Source" ),
T ( "Kerberos/SSO ERI data sources cannot be exported yet. Please configure them manually in the configuration plugin." ),
T ( "Close" ));
return ;
}
var needsSecret = eriDataSource . AuthMethod is AuthMethod . TOKEN or AuthMethod . USERNAME_PASSWORD ;
if (! needsSecret )
{
var publicLuaCode = eriDataSource . ExportAsConfigurationSection ();
if (! string . IsNullOrWhiteSpace ( publicLuaCode ))
2026-08-02 21:41:12 +02:00
await this . RustService . CopyText2Clipboard ( publicLuaCode );
2026-05-18 16:26:51 +02:00
return ;
}
var secretResponse = await this . RustService . GetSecret ( eriDataSource , SecretStoreType . DATA_SOURCE , isTrying : true );
if (! secretResponse . Success )
{
await this . DialogService . ShowMessageBox (
T ( "Export ERI Data Source" ),
string . Format ( T ( "Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}" ), secretResponse . Issue ),
T ( "Close" ));
return ;
}
var encryption = PluginFactory . EnterpriseEncryption ;
if ( encryption ?. IsAvailable != true )
{
await this . DialogService . ShowMessageBox (
T ( "Export ERI Data Source" ),
T ( "Cannot export this ERI data source because no enterprise encryption secret is configured." ),
T ( "Close" ));
return ;
}
var usernamePasswordMode = DataSourceERIUsernamePasswordMode . USER_MANAGED ;
if ( eriDataSource . AuthMethod is AuthMethod . TOKEN )
{
var dialogParameters = new DialogParameters < ConfirmDialog >
{
{ x => x . Message , T ( "This ERI data source has an access token configured. Do you want to include the encrypted access token in the export? Note: The recipient will need the same encryption secret to use the access token." ) },
};
var dialogReference = await this . DialogService . ShowAsync < ConfirmDialog >( T ( "Export Access Token?" ), dialogParameters , DialogOptions . FULLSCREEN );
var dialogResult = await dialogReference . Result ;
if ( dialogResult is null || dialogResult . Canceled )
return ;
}
else if ( eriDataSource . AuthMethod is AuthMethod . USERNAME_PASSWORD )
{
var dialogParameters = new DialogParameters < DataSourceERIV1UsernamePasswordExportDialog >
{
{ x => x . DataSource , eriDataSource },
};
var dialogReference = await this . DialogService . ShowAsync < DataSourceERIV1UsernamePasswordExportDialog >( T ( "Export ERI Data Source" ), dialogParameters , DialogOptions . FULLSCREEN );
var dialogResult = await dialogReference . Result ;
if ( dialogResult is null || dialogResult . Canceled || dialogResult . Data is not DataSourceERIV1UsernamePasswordExportDialogResult exportResult )
return ;
usernamePasswordMode = exportResult . UsernamePasswordMode ;
}
var decryptedSecret = await secretResponse . Secret . Decrypt ( Program . ENCRYPTION );
if (! encryption . TryEncrypt ( decryptedSecret , out var encryptedSecret ))
{
await this . DialogService . ShowMessageBox (
T ( "Export ERI Data Source" ),
T ( "Cannot export this ERI data source because the authentication secret could not be encrypted." ),
T ( "Close" ));
return ;
}
var luaCode = eriDataSource . ExportAsConfigurationSection (
encryptedSecret ,
usernamePasswordMode );
if ( string . IsNullOrWhiteSpace ( luaCode ))
return ;
2026-08-02 21:41:12 +02:00
await this . RustService . CopyText2Clipboard ( luaCode );
2026-05-18 16:26:51 +02:00
}
2025-01-13 19:51:26 +01:00
private async Task EditDataSource ( IDataSource dataSource )
{
2026-05-18 16:26:51 +02:00
if ( dataSource . IsEnterpriseConfiguration )
return ;
2025-01-13 19:51:26 +01:00
IDataSource ? editedDataSource = null ;
2026-08-03 17:53:31 +02:00
var lockDataSourceIdentity = dataSource is IInternalDataSource
&& await this . DataSourceEmbeddingService . ShouldLockDataSourceIdentityAsync ( dataSource . Id );
2025-01-13 19:51:26 +01:00
switch ( dataSource )
{
case DataSourceLocalFile localFile :
var localFileDialogParameters = new DialogParameters < DataSourceLocalFileDialog >
{
{ x => x . IsEditing , true },
{ x => x . DataSource , localFile },
2026-08-03 17:53:31 +02:00
{ x => x . LockSourceAndEmbedding , lockDataSourceIdentity },
2026-04-16 16:48:47 +02:00
{ x => x . AvailableEmbeddings , this . AvailableEmbeddingProviders }
2025-01-13 19:51:26 +01:00
};
2025-04-24 13:50:14 +02:00
var localFileDialogReference = await this . DialogService . ShowAsync < DataSourceLocalFileDialog >( T ( "Edit Local File Data Source" ), localFileDialogParameters , DialogOptions . FULLSCREEN );
2025-01-13 19:51:26 +01:00
var localFileDialogResult = await localFileDialogReference . Result ;
if ( localFileDialogResult is null || localFileDialogResult . Canceled )
return ;
editedDataSource = ( DataSourceLocalFile ) localFileDialogResult . Data !;
break ;
case DataSourceLocalDirectory localDirectory :
var localDirectoryDialogParameters = new DialogParameters < DataSourceLocalDirectoryDialog >
{
{ x => x . IsEditing , true },
{ x => x . DataSource , localDirectory },
2026-08-03 17:53:31 +02:00
{ x => x . LockSourceAndEmbedding , lockDataSourceIdentity },
2026-04-16 16:48:47 +02:00
{ x => x . AvailableEmbeddings , this . AvailableEmbeddingProviders }
2025-01-13 19:51:26 +01:00
};
2025-04-24 13:50:14 +02:00
var localDirectoryDialogReference = await this . DialogService . ShowAsync < DataSourceLocalDirectoryDialog >( T ( "Edit Local Directory Data Source" ), localDirectoryDialogParameters , DialogOptions . FULLSCREEN );
2025-01-13 19:51:26 +01:00
var localDirectoryDialogResult = await localDirectoryDialogReference . Result ;
if ( localDirectoryDialogResult is null || localDirectoryDialogResult . Canceled )
return ;
editedDataSource = ( DataSourceLocalDirectory ) localDirectoryDialogResult . Data !;
break ;
case DataSourceERI_V1 eriDataSource :
var eriDialogParameters = new DialogParameters < DataSourceERI_V1Dialog >
{
{ x => x . IsEditing , true },
{ x => x . DataSource , eriDataSource },
};
2025-04-24 13:50:14 +02:00
var eriDialogReference = await this . DialogService . ShowAsync < DataSourceERI_V1Dialog >( T ( "Edit ERI v1 Data Source" ), eriDialogParameters , DialogOptions . FULLSCREEN );
2025-01-13 19:51:26 +01:00
var eriDialogResult = await eriDialogReference . Result ;
if ( eriDialogResult is null || eriDialogResult . Canceled )
return ;
editedDataSource = ( DataSourceERI_V1 ) eriDialogResult . Data !;
break ;
}
if ( editedDataSource is null )
return ;
this . SettingsManager . ConfigurationData . DataSources [ this . SettingsManager . ConfigurationData . DataSources . IndexOf ( dataSource )] = editedDataSource ;
await this . SettingsManager . StoreSettings ();
2026-05-08 17:37:34 +02:00
await this . DataSourceEmbeddingService . QueueDataSourceAsync ( editedDataSource );
2025-01-13 19:51:26 +01:00
await this . MessageBus . SendMessage < bool >( this , Event . CONFIGURATION_CHANGED );
}
private async Task DeleteDataSource ( IDataSource dataSource )
{
2026-05-18 16:26:51 +02:00
if ( dataSource . IsEnterpriseConfiguration )
return ;
2025-08-28 18:51:44 +02:00
var dialogParameters = new DialogParameters < ConfirmDialog >
2025-01-13 19:51:26 +01:00
{
2026-07-28 15:25:10 +02:00
{ x => x . Message , string . Format ( T ( "Are you sure you want to delete the data source '{0}' of type '{1}'?" ), dataSource . Name , dataSource . Type . GetDisplayName ()) },
2025-01-13 19:51:26 +01:00
};
2025-04-24 13:50:14 +02:00
var dialogReference = await this . DialogService . ShowAsync < ConfirmDialog >( T ( "Delete Data Source" ), dialogParameters , DialogOptions . FULLSCREEN );
2025-01-13 19:51:26 +01:00
var dialogResult = await dialogReference . Result ;
if ( dialogResult is null || dialogResult . Canceled )
return ;
var applyChanges = dataSource is IInternalDataSource ;
// External data sources may need a secret for authentication:
if ( dataSource is IExternalDataSource externalDataSource )
{
// When the auth method is NONE or KERBEROS, we don't need to delete a secret.
// In the case of KERBEROS, we don't store the Kerberos ticket in the secret store.
if ( dataSource is IERIDataSource { AuthMethod : AuthMethod . NONE or AuthMethod . KERBEROS })
applyChanges = true ;
// All other auth methods require a secret, which we need to delete now:
else
{
2026-05-18 16:26:51 +02:00
var deleteSecretResponse = await this . RustService . DeleteSecret ( externalDataSource , SecretStoreType . DATA_SOURCE );
2025-01-13 19:51:26 +01:00
if ( deleteSecretResponse . Success )
applyChanges = true ;
}
}
if ( applyChanges )
{
this . SettingsManager . ConfigurationData . DataSources . Remove ( dataSource );
await this . SettingsManager . StoreSettings ();
2026-05-08 17:37:34 +02:00
await this . DataSourceEmbeddingService . RemoveDataSourceAsync ( dataSource );
2025-01-13 19:51:26 +01:00
await this . MessageBus . SendMessage < bool >( this , Event . CONFIGURATION_CHANGED );
}
}
2025-02-09 12:36:37 +01:00
private async Task ShowInformation ( IDataSource dataSource )
2025-01-13 19:51:26 +01:00
{
2025-02-09 12:36:37 +01:00
switch ( dataSource )
{
case DataSourceLocalFile localFile :
var localFileDialogParameters = new DialogParameters < DataSourceLocalFileInfoDialog >
{
{ x => x . DataSource , localFile },
};
2025-04-24 13:50:14 +02:00
await this . DialogService . ShowAsync < DataSourceLocalFileInfoDialog >( T ( "Local File Data Source Information" ), localFileDialogParameters , DialogOptions . FULLSCREEN );
2025-02-09 12:36:37 +01:00
break ;
case DataSourceLocalDirectory localDirectory :
var localDirectoryDialogParameters = new DialogParameters < DataSourceLocalDirectoryInfoDialog >
{
{ x => x . DataSource , localDirectory },
};
2025-04-24 13:50:14 +02:00
await this . DialogService . ShowAsync < DataSourceLocalDirectoryInfoDialog >( T ( "Local Directory Data Source Information" ), localDirectoryDialogParameters , DialogOptions . FULLSCREEN );
2025-02-09 12:36:37 +01:00
break ;
case DataSourceERI_V1 eriV1DataSource :
var eriV1DialogParameters = new DialogParameters < DataSourceERI_V1InfoDialog >
{
{ x => x . DataSource , eriV1DataSource },
};
2025-04-24 13:50:14 +02:00
await this . DialogService . ShowAsync < DataSourceERI_V1InfoDialog >( T ( "ERI v1 Data Source Information" ), eriV1DialogParameters , DialogOptions . FULLSCREEN );
2025-02-09 12:36:37 +01:00
break ;
}
2025-01-13 19:51:26 +01:00
}
2026-05-08 17:37:34 +02:00
}