From 4c69434c47dcb7e46dc1c7c9d359c3f307d3859f Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 6 Sep 2026 20:34:18 +0200 Subject: [PATCH] Add a data sources panel to the app settings --- .../Assistants/I18N/allTexts.lua | 276 +++++------ .../Components/DataSourceManagement.razor | 105 +++++ .../Components/DataSourceManagement.razor.cs | 435 ++++++++++++++++++ .../Settings/SettingsPanelDataSources.razor | 10 + .../SettingsPanelDataSources.razor.cs | 3 + .../Settings/SettingsDialogDataSources.razor | 114 +---- .../SettingsDialogDataSources.razor.cs | 391 +--------------- app/MindWork AI Studio/Pages/Settings.razor | 1 + 8 files changed, 701 insertions(+), 634 deletions(-) create mode 100644 app/MindWork AI Studio/Components/DataSourceManagement.razor create mode 100644 app/MindWork AI Studio/Components/DataSourceManagement.razor.cs create mode 100644 app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor create mode 100644 app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index a56cc774..494f8cb5 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -3703,6 +3703,144 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCECLOUDEMBEDDINGWARNING::T6617545 -- All files in this folder and its subfolders UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCECLOUDEMBEDDINGWARNING::T916879200"] = "All files in this folder and its subfolders" +-- You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1084943026"] = "You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task." + +-- Automatic local data source refresh +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1208397349"] = "Automatic local data source refresh" + +-- Edit Local Directory Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1215599168"] = "Edit Local Directory Data Source" + +-- Refresh +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T135637716"] = "Refresh" + +-- Add Local Directory as Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1454193397"] = "Add Local Directory as Data Source" + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1469573738"] = "Delete" + +-- Refresh all +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1503082343"] = "Refresh all" + +-- Kerberos/SSO ERI data sources cannot be exported yet. Please configure them manually in the configuration plugin. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1577531115"] = "Kerberos/SSO ERI data sources cannot be exported yet. Please configure them manually in the configuration plugin." + +-- Cannot export this ERI data source because the authentication secret could not be encrypted. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1592527757"] = "Cannot export this ERI data source because the authentication secret could not be encrypted." + +-- External (ERI) +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1652430727"] = "External (ERI)" + +-- Local File +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1687345358"] = "Local File" + +-- Delete Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1849107431"] = "Delete Data Source" + +-- Local Directory Data Source Information +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2146756020"] = "Local Directory Data Source Information" + +-- Edit ERI v1 Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T221059217"] = "Edit ERI v1 Data Source" + +-- Indexed files +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2235289713"] = "Indexed files" + +-- Edit Local File Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2453292893"] = "Edit Local File Data Source" + +-- ERI v1 Data Source Information +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T26243729"] = "ERI v1 Data Source Information" + +-- Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T266367750"] = "Name" + +-- Not applicable +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2675917723"] = "Not applicable" + +-- No valid embedding +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2698203405"] = "No valid embedding" + +-- Embedding +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2838542994"] = "Embedding" + +-- This data source is managed by your organization. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3031462878"] = "This data source is managed by your organization." + +-- Edit +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3267849393"] = "Edit" + +-- Are you sure you want to delete the data source '{0}' of type '{1}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3337072977"] = "Are you sure you want to delete the data source '{0}' of type '{1}'?" + +-- Add Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3387511033"] = "Add Data Source" + +-- Unknown +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3424652889"] = "Unknown" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3448155331"] = "Close" + +-- Add Local File as Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3500365052"] = "Add Local File as Data Source" + +-- Type +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3512062061"] = "Type" + +-- Local File Data Source Information +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3525663993"] = "Local File Data Source Information" + +-- No data sources configured yet. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3549650120"] = "No data sources configured yet." + +-- Export Access Token? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3595669127"] = "Export Access Token?" + +-- Local data sources refresh when files change. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3687976654"] = "Local data sources refresh when files change." + +-- Not available +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3706935413"] = "Not available" + +-- Export ERI Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3831281036"] = "Export ERI Data Source" + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T3865031940"] = "Actions" + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T4027572258"] = "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." + +-- Waiting for indexing status +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T4108252513"] = "Waiting for indexing status" + +-- Information +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T4256323669"] = "Information" + +-- Add ERI v1 Data Source +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T590005498"] = "Add ERI v1 Data Source" + +-- Cannot export this ERI data source because no enterprise encryption secret is configured. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T750361472"] = "Cannot export this ERI data source because no enterprise encryption secret is configured." + +-- External Data (ERI-Server v1) +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T774473996"] = "External Data (ERI-Server v1)" + +-- Cannot export this ERI data source because no authentication secret is configured. The issue was: {0} +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T782820095"] = "Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}" + +-- {0} of {1} +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T825342513"] = "{0} of {1}" + +-- Local data sources refresh only when triggered manually. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T854231603"] = "Local data sources refresh only when triggered manually." + +-- Local Directory +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T926703547"] = "Local Directory" + -- Yes, let the AI decide which data sources are needed. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1031370894"] = "Yes, let the AI decide which data sources are needed." @@ -4552,6 +4690,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T85322 -- Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T900237532"] = "Provider" +-- Configure Data Sources +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELDATASOURCES::T476193103"] = "Configure Data Sources" + -- Embedding Result UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1387042335"] = "Embedding Result" @@ -7510,147 +7651,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T516498299"] -- Assistant: Coding Options UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T585868261"] = "Assistant: Coding Options" --- You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1084943026"] = "You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task." - --- Automatic local data source refresh -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1208397349"] = "Automatic local data source refresh" - --- Edit Local Directory Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1215599168"] = "Edit Local Directory Data Source" - --- Refresh -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T135637716"] = "Refresh" - --- Add Local Directory as Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1454193397"] = "Add Local Directory as Data Source" - --- Delete -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1469573738"] = "Delete" - --- Refresh all -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1503082343"] = "Refresh all" - --- Kerberos/SSO ERI data sources cannot be exported yet. Please configure them manually in the configuration plugin. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1577531115"] = "Kerberos/SSO ERI data sources cannot be exported yet. Please configure them manually in the configuration plugin." - --- Cannot export this ERI data source because the authentication secret could not be encrypted. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1592527757"] = "Cannot export this ERI data source because the authentication secret could not be encrypted." - --- External (ERI) -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1652430727"] = "External (ERI)" - --- Local File -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1687345358"] = "Local File" - --- Delete Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1849107431"] = "Delete Data Source" - --- Local Directory Data Source Information -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2146756020"] = "Local Directory Data Source Information" - --- Edit ERI v1 Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T221059217"] = "Edit ERI v1 Data Source" - --- Indexed files -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2235289713"] = "Indexed files" - --- Edit Local File Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2453292893"] = "Edit Local File Data Source" - --- ERI v1 Data Source Information -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T26243729"] = "ERI v1 Data Source Information" - --- Name -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T266367750"] = "Name" - --- Not applicable -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2675917723"] = "Not applicable" - --- No valid embedding -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2698203405"] = "No valid embedding" - --- Embedding -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2838542994"] = "Embedding" - --- This data source is managed by your organization. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3031462878"] = "This data source is managed by your organization." - --- Edit -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3267849393"] = "Edit" - --- Are you sure you want to delete the data source '{0}' of type '{1}'? -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3337072977"] = "Are you sure you want to delete the data source '{0}' of type '{1}'?" - --- Add Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3387511033"] = "Add Data Source" - --- Unknown -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3424652889"] = "Unknown" - -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3448155331"] = "Close" --- Add Local File as Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3500365052"] = "Add Local File as Data Source" - --- Type -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3512062061"] = "Type" - --- Local File Data Source Information -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3525663993"] = "Local File Data Source Information" - --- No data sources configured yet. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3549650120"] = "No data sources configured yet." - --- Export Access Token? -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3595669127"] = "Export Access Token?" - --- Local data sources refresh when files change. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3687976654"] = "Local data sources refresh when files change." - --- Not available -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3706935413"] = "Not available" - --- Export ERI Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3831281036"] = "Export ERI Data Source" - --- Actions -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3865031940"] = "Actions" - --- 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. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T4027572258"] = "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." - --- Waiting for indexing status -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T4108252513"] = "Waiting for indexing status" - --- Information -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T4256323669"] = "Information" - -- Configured Data Sources UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T543942217"] = "Configured Data Sources" --- Add ERI v1 Data Source -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T590005498"] = "Add ERI v1 Data Source" - --- Cannot export this ERI data source because no enterprise encryption secret is configured. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T750361472"] = "Cannot export this ERI data source because no enterprise encryption secret is configured." - --- External Data (ERI-Server v1) -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T774473996"] = "External Data (ERI-Server v1)" - --- Cannot export this ERI data source because no authentication secret is configured. The issue was: {0} -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T782820095"] = "Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}" - --- {0} of {1} -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T825342513"] = "{0} of {1}" - --- Local data sources refresh only when triggered manually. -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T854231603"] = "Local data sources refresh only when triggered manually." - --- Local Directory -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T926703547"] = "Local Directory" - -- When enabled, you can preselect some ERI server options. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1280666275"] = "When enabled, you can preselect some ERI server options." diff --git a/app/MindWork AI Studio/Components/DataSourceManagement.razor b/app/MindWork AI Studio/Components/DataSourceManagement.razor new file mode 100644 index 00000000..c8d28ad1 --- /dev/null +++ b/app/MindWork AI Studio/Components/DataSourceManagement.razor @@ -0,0 +1,105 @@ +@using AIStudio.Settings +@using AIStudio.Settings.DataModel +@inherits MSGComponentBase + + + @T("You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task.") + + + + + + + + + +@{ var embeddingStatuses = this.DataSourceEmbeddingService.GetStatuses().ToDictionary(status => status.DataSourceId, StringComparer.OrdinalIgnoreCase); } + + + + + + + + + + + # + @T("Name") + @T("Type") + @T("Embedding") + @T("Indexed files") + @T("Actions") + + + @{ var embeddingStatus = embeddingStatuses.GetValueOrDefault(context.Id); } + @context.Num + @context.Name + @context.Type.GetDisplayName() + @this.GetEmbeddingName(context) + + @if (context is IInternalDataSource) + { + + + + @(embeddingStatus is null ? T("Not available") : string.Format(T("{0} of {1}"), embeddingStatus.IndexedFiles, embeddingStatus.TotalFiles)) + + + } + else + { + @T("Not applicable") + } + + + + + + + + @if (context.IsEnterpriseConfiguration) + { + + + + } + else + { + + + + + + + @if (context is DataSourceERI_V1) + { + + } + + + + } + + + + + +@if (this.SettingsManager.ConfigurationData.DataSources.Count == 0) +{ + + @T("No data sources configured yet.") + +} + + + + @T("External Data (ERI-Server v1)") + + + @T("Local Directory") + + + @T("Local File") + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/DataSourceManagement.razor.cs b/app/MindWork AI Studio/Components/DataSourceManagement.razor.cs new file mode 100644 index 00000000..46042fa5 --- /dev/null +++ b/app/MindWork AI Studio/Components/DataSourceManagement.razor.cs @@ -0,0 +1,435 @@ +using AIStudio.Dialogs; +using AIStudio.Settings; +using AIStudio.Settings.DataModel; +using AIStudio.Tools.ERIClient.DataModel; +using AIStudio.Tools.PluginSystem; +using AIStudio.Tools.Services; + +using Microsoft.AspNetCore.Components; + +using DialogOptions = AIStudio.Dialogs.DialogOptions; + +namespace AIStudio.Components; + +/// +/// Manages the configured data sources. Used by the data source settings dialog, which the chat +/// opens, and by the data source panel in the app settings. +/// +public partial class DataSourceManagement : MSGComponentBase +{ + [Inject] + private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!; + + [Inject] + private IDialogService DialogService { get; init; } = null!; + + [Inject] + private RustService RustService { get; init; } = null!; + + private readonly List> availableEmbeddingProviders = new(); + + #region Overrides of ComponentBase + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.RAG_EMBEDDING_STATUS_CHANGED ]); + this.UpdateEmbeddingProviders(); + } + + #endregion + + #region Overrides of MSGComponentBase + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.CONFIGURATION_CHANGED: + case Event.PLUGINS_RELOADED: + this.UpdateEmbeddingProviders(); + this.StateHasChanged(); + break; + + case Event.RAG_EMBEDDING_STATUS_CHANGED: + this.StateHasChanged(); + break; + } + + return Task.CompletedTask; + } + + #endregion + + private void UpdateEmbeddingProviders() + { + this.availableEmbeddingProviders.Clear(); + foreach (var provider in this.SettingsManager.GetAllEmbeddingProviders()) + this.availableEmbeddingProviders.Add(new (provider.Name, provider.Id)); + } + + 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; + } + + 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) + return T("No valid embedding"); + + return matchedEmbedding.Name; + } + + if(dataSource is IExternalDataSource) + return T("External (ERI)"); + + return T("Unknown"); + } + + private bool CanRefreshDataSource(IDataSource dataSource) + { + return this.DataSourceEmbeddingService.CanRefreshDataSource(dataSource); + } + + private bool HasRefreshableDataSources() + { + return this.SettingsManager.ConfigurationData.DataSources.Any(this.CanRefreshDataSource); + } + + private async Task AutomaticRefreshChanged(bool enabled) + { + this.SettingsManager.ConfigurationData.App.DataSourceIndexing.AutomaticRefresh = enabled; + await this.SettingsManager.StoreSettings(); + this.DataSourceEmbeddingService.RefreshAutomaticWatchers(); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + + private async Task RefreshAllDataSources() + { + await this.DataSourceEmbeddingService.QueueAllInternalDataSourcesAsync(); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + + private async Task RefreshDataSource(IDataSource dataSource) + { + if (!this.CanRefreshDataSource(dataSource)) + return; + + await this.DataSourceEmbeddingService.QueueDataSourceAsync(dataSource); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + + private async Task AddDataSource(DataSourceType type) + { + IDataSource? addedDataSource = null; + switch (type) + { + case DataSourceType.LOCAL_FILE: + var localFileDialogParameters = new DialogParameters + { + { x => x.IsEditing, false }, + { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } + }; + + var localFileDialogReference = await this.DialogService.ShowAsync(T("Add Local File as Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN); + 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 + { + { x => x.IsEditing, false }, + { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } + }; + + var localDirectoryDialogReference = await this.DialogService.ShowAsync(T("Add Local Directory as Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); + 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 + { + { x => x.IsEditing, false }, + }; + + var eriDialogReference = await this.DialogService.ShowAsync(T("Add ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN); + 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(); + await this.DataSourceEmbeddingService.QueueDataSourceAsync(addedDataSource); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + + 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)) + await this.RustService.CopyText2Clipboard(publicLuaCode); + + 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 + { + { 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(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 + { + { x => x.DataSource, eriDataSource }, + }; + + var dialogReference = await this.DialogService.ShowAsync(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; + + await this.RustService.CopyText2Clipboard(luaCode); + } + + private async Task EditDataSource(IDataSource dataSource) + { + if (dataSource.IsEnterpriseConfiguration) + return; + + IDataSource? editedDataSource = null; + var lockDataSourceIdentity = dataSource is IInternalDataSource + && await this.DataSourceEmbeddingService.ShouldLockDataSourceIdentityAsync(dataSource.Id); + switch (dataSource) + { + case DataSourceLocalFile localFile: + var localFileDialogParameters = new DialogParameters + { + { x => x.IsEditing, true }, + { x => x.DataSource, localFile }, + { x => x.LockSourceAndEmbedding, lockDataSourceIdentity }, + { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } + }; + + var localFileDialogReference = await this.DialogService.ShowAsync(T("Edit Local File Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN); + var localFileDialogResult = await localFileDialogReference.Result; + if (localFileDialogResult is null || localFileDialogResult.Canceled) + return; + + editedDataSource = (DataSourceLocalFile)localFileDialogResult.Data!; + break; + + case DataSourceLocalDirectory localDirectory: + var localDirectoryDialogParameters = new DialogParameters + { + { x => x.IsEditing, true }, + { x => x.DataSource, localDirectory }, + { x => x.LockSourceAndEmbedding, lockDataSourceIdentity }, + { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } + }; + + var localDirectoryDialogReference = await this.DialogService.ShowAsync(T("Edit Local Directory Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); + 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 + { + { x => x.IsEditing, true }, + { x => x.DataSource, eriDataSource }, + }; + + var eriDialogReference = await this.DialogService.ShowAsync(T("Edit ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN); + 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(); + await this.DataSourceEmbeddingService.QueueDataSourceAsync(editedDataSource); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + + private async Task DeleteDataSource(IDataSource dataSource) + { + if (dataSource.IsEnterpriseConfiguration) + return; + + var dialogParameters = new DialogParameters + { + { 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()) }, + }; + + var dialogReference = await this.DialogService.ShowAsync(T("Delete Data Source"), dialogParameters, DialogOptions.FULLSCREEN); + 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 + { + var deleteSecretResponse = await this.RustService.DeleteSecret(externalDataSource, SecretStoreType.DATA_SOURCE); + if (deleteSecretResponse.Success) + applyChanges = true; + } + } + + if(applyChanges) + { + this.SettingsManager.ConfigurationData.DataSources.Remove(dataSource); + await this.SettingsManager.StoreSettings(); + await this.DataSourceEmbeddingService.RemoveDataSourceAsync(dataSource); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + } + + private async Task ShowInformation(IDataSource dataSource) + { + switch (dataSource) + { + case DataSourceLocalFile localFile: + var localFileDialogParameters = new DialogParameters + { + { x => x.DataSource, localFile }, + }; + + await this.DialogService.ShowAsync(T("Local File Data Source Information"), localFileDialogParameters, DialogOptions.FULLSCREEN); + break; + + case DataSourceLocalDirectory localDirectory: + var localDirectoryDialogParameters = new DialogParameters + { + { x => x.DataSource, localDirectory }, + }; + + await this.DialogService.ShowAsync(T("Local Directory Data Source Information"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); + break; + + case DataSourceERI_V1 eriV1DataSource: + var eriV1DialogParameters = new DialogParameters + { + { x => x.DataSource, eriV1DataSource }, + }; + + await this.DialogService.ShowAsync(T("ERI v1 Data Source Information"), eriV1DialogParameters, DialogOptions.FULLSCREEN); + break; + } + } +} diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor new file mode 100644 index 00000000..9d0e92f0 --- /dev/null +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor @@ -0,0 +1,10 @@ +@using AIStudio.Settings.DataModel +@inherits SettingsPanelBase + +@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) +{ + + + + +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs new file mode 100644 index 00000000..41fcc88b --- /dev/null +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Components.Settings; + +public partial class SettingsPanelDataSources : SettingsPanelBase; \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor index ec707d1b..b278e115 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor @@ -1,5 +1,3 @@ -@using AIStudio.Settings -@using AIStudio.Settings.DataModel @inherits SettingsDialogBase @@ -11,113 +9,11 @@ - - @T("You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task.") - - - - - - - - - - @{ var embeddingStatuses = this.DataSourceEmbeddingService.GetStatuses().ToDictionary(status => status.DataSourceId, StringComparer.OrdinalIgnoreCase); } - - - - - - - - - - - # - @T("Name") - @T("Type") - @T("Embedding") - @T("Indexed files") - @T("Actions") - - - @{ var embeddingStatus = embeddingStatuses.GetValueOrDefault(context.Id); } - @context.Num - @context.Name - @context.Type.GetDisplayName() - @this.GetEmbeddingName(context) - - @if (context is IInternalDataSource) - { - - - - @(embeddingStatus is null ? T("Not available") : string.Format(T("{0} of {1}"), embeddingStatus.IndexedFiles, embeddingStatus.TotalFiles)) - - - } - else - { - @T("Not applicable") - } - - - - - - - - @if (context.IsEnterpriseConfiguration) - { - - - - } - else - { - - - - - - - @if (context is DataSourceERI_V1) - { - - } - - - - } - - - - - - @if (this.SettingsManager.ConfigurationData.DataSources.Count == 0) - { - - @T("No data sources configured yet.") - - } - + - - - - @T("External Data (ERI-Server v1)") - - - @T("Local Directory") - - - @T("Local File") - - - - @T("Close") - - + + @T("Close") + - + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs index 25680fa9..995c1de2 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs @@ -1,392 +1,3 @@ -using AIStudio.Settings; -using AIStudio.Settings.DataModel; -using AIStudio.Tools.ERIClient.DataModel; -using AIStudio.Tools.PluginSystem; -using AIStudio.Tools.Services; -using Microsoft.AspNetCore.Components; - namespace AIStudio.Dialogs.Settings; -public partial class SettingsDialogDataSources : SettingsDialogBase -{ - [Inject] - private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!; - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.RAG_EMBEDDING_STATUS_CHANGED ]); - } - - protected override async Task ProcessIncomingMessage(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; - } - - 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) - return T("No valid embedding"); - - return matchedEmbedding.Name; - } - - if(dataSource is IExternalDataSource) - return T("External (ERI)"); - - return T("Unknown"); - } - - private bool CanRefreshDataSource(IDataSource dataSource) - { - return this.DataSourceEmbeddingService.CanRefreshDataSource(dataSource); - } - - private bool HasRefreshableDataSources() - { - return this.SettingsManager.ConfigurationData.DataSources.Any(this.CanRefreshDataSource); - } - - private async Task AutomaticRefreshChanged(bool enabled) - { - this.SettingsManager.ConfigurationData.App.DataSourceIndexing.AutomaticRefresh = enabled; - await this.SettingsManager.StoreSettings(); - this.DataSourceEmbeddingService.RefreshAutomaticWatchers(); - await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - } - - private async Task RefreshAllDataSources() - { - await this.DataSourceEmbeddingService.QueueAllInternalDataSourcesAsync(); - await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - } - - private async Task RefreshDataSource(IDataSource dataSource) - { - if (!this.CanRefreshDataSource(dataSource)) - return; - - await this.DataSourceEmbeddingService.QueueDataSourceAsync(dataSource); - await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - } - - private async Task AddDataSource(DataSourceType type) - { - IDataSource? addedDataSource = null; - switch (type) - { - case DataSourceType.LOCAL_FILE: - var localFileDialogParameters = new DialogParameters - { - { x => x.IsEditing, false }, - { x => x.AvailableEmbeddings, this.AvailableEmbeddingProviders } - }; - - var localFileDialogReference = await this.DialogService.ShowAsync(T("Add Local File as Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN); - 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 - { - { x => x.IsEditing, false }, - { x => x.AvailableEmbeddings, this.AvailableEmbeddingProviders } - }; - - var localDirectoryDialogReference = await this.DialogService.ShowAsync(T("Add Local Directory as Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); - 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 - { - { x => x.IsEditing, false }, - }; - - var eriDialogReference = await this.DialogService.ShowAsync(T("Add ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN); - 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(); - await this.DataSourceEmbeddingService.QueueDataSourceAsync(addedDataSource); - await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - } - - 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)) - await this.RustService.CopyText2Clipboard(publicLuaCode); - - 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 - { - { 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(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 - { - { x => x.DataSource, eriDataSource }, - }; - - var dialogReference = await this.DialogService.ShowAsync(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; - - await this.RustService.CopyText2Clipboard(luaCode); - } - - private async Task EditDataSource(IDataSource dataSource) - { - if (dataSource.IsEnterpriseConfiguration) - return; - - IDataSource? editedDataSource = null; - var lockDataSourceIdentity = dataSource is IInternalDataSource - && await this.DataSourceEmbeddingService.ShouldLockDataSourceIdentityAsync(dataSource.Id); - switch (dataSource) - { - case DataSourceLocalFile localFile: - var localFileDialogParameters = new DialogParameters - { - { x => x.IsEditing, true }, - { x => x.DataSource, localFile }, - { x => x.LockSourceAndEmbedding, lockDataSourceIdentity }, - { x => x.AvailableEmbeddings, this.AvailableEmbeddingProviders } - }; - - var localFileDialogReference = await this.DialogService.ShowAsync(T("Edit Local File Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN); - var localFileDialogResult = await localFileDialogReference.Result; - if (localFileDialogResult is null || localFileDialogResult.Canceled) - return; - - editedDataSource = (DataSourceLocalFile)localFileDialogResult.Data!; - break; - - case DataSourceLocalDirectory localDirectory: - var localDirectoryDialogParameters = new DialogParameters - { - { x => x.IsEditing, true }, - { x => x.DataSource, localDirectory }, - { x => x.LockSourceAndEmbedding, lockDataSourceIdentity }, - { x => x.AvailableEmbeddings, this.AvailableEmbeddingProviders } - }; - - var localDirectoryDialogReference = await this.DialogService.ShowAsync(T("Edit Local Directory Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); - 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 - { - { x => x.IsEditing, true }, - { x => x.DataSource, eriDataSource }, - }; - - var eriDialogReference = await this.DialogService.ShowAsync(T("Edit ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN); - 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(); - await this.DataSourceEmbeddingService.QueueDataSourceAsync(editedDataSource); - await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - } - - private async Task DeleteDataSource(IDataSource dataSource) - { - if (dataSource.IsEnterpriseConfiguration) - return; - - var dialogParameters = new DialogParameters - { - { 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()) }, - }; - - var dialogReference = await this.DialogService.ShowAsync(T("Delete Data Source"), dialogParameters, DialogOptions.FULLSCREEN); - 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 - { - var deleteSecretResponse = await this.RustService.DeleteSecret(externalDataSource, SecretStoreType.DATA_SOURCE); - if (deleteSecretResponse.Success) - applyChanges = true; - } - } - - if(applyChanges) - { - this.SettingsManager.ConfigurationData.DataSources.Remove(dataSource); - await this.SettingsManager.StoreSettings(); - await this.DataSourceEmbeddingService.RemoveDataSourceAsync(dataSource); - await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - } - } - - private async Task ShowInformation(IDataSource dataSource) - { - switch (dataSource) - { - case DataSourceLocalFile localFile: - var localFileDialogParameters = new DialogParameters - { - { x => x.DataSource, localFile }, - }; - - await this.DialogService.ShowAsync(T("Local File Data Source Information"), localFileDialogParameters, DialogOptions.FULLSCREEN); - break; - - case DataSourceLocalDirectory localDirectory: - var localDirectoryDialogParameters = new DialogParameters - { - { x => x.DataSource, localDirectory }, - }; - - await this.DialogService.ShowAsync(T("Local Directory Data Source Information"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); - break; - - case DataSourceERI_V1 eriV1DataSource: - var eriV1DialogParameters = new DialogParameters - { - { x => x.DataSource, eriV1DataSource }, - }; - - await this.DialogService.ShowAsync(T("ERI v1 Data Source Information"), eriV1DialogParameters, DialogOptions.FULLSCREEN); - break; - } - } -} +public partial class SettingsDialogDataSources : SettingsDialogBase; \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor index 7718ba9b..90c5f6ff 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor +++ b/app/MindWork AI Studio/Pages/Settings.razor @@ -13,6 +13,7 @@ @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) { + }