diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index acfd2fce..06b6fb92 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -59,6 +59,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private DataSourceSelection? dataSourceSelectionComponent; private DataSourceOptions earlyDataSourceOptions = new(); + private DataSourceOptions lastAppliedStandardDataSourceOptions = new(); private Profile currentProfile = Profile.NO_PROFILE; private ChatTemplate currentChatTemplate = ChatTemplate.NO_CHAT_TEMPLATE; private bool hasUnsavedChanges; @@ -118,6 +119,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable if (!this.ComposerState.HasUserDraft && !this.ComposerState.HasComposerContent) this.ComposerState.ApplyTemplate(this.currentChatTemplate); + this.lastAppliedStandardDataSourceOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy(); + var deferredInput = MessageBus.INSTANCE.CheckDeferredMessages(Event.SEND_TO_CHAT_INPUT).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(deferredInput)) this.ComposerState.SetUserInput(deferredInput); @@ -458,12 +461,42 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private void ApplyStandardDataSourceOptions() { var chatDefaultOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy(); + this.lastAppliedStandardDataSourceOptions = chatDefaultOptions.CreateCopy(); this.earlyDataSourceOptions = chatDefaultOptions; if(this.ChatThread is not null) this.ChatThread.DataSourceOptions = chatDefaultOptions; this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(chatDefaultOptions); } + + private async Task ApplyUpdatedStandardDataSourceOptionsAfterConfigurationChange() + { + var updatedStandardOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy(); + var previousStandardOptions = this.lastAppliedStandardDataSourceOptions; + this.lastAppliedStandardDataSourceOptions = updatedStandardOptions.CreateCopy(); + + if (this.ChatThread is null) + { + this.earlyDataSourceOptions = updatedStandardOptions; + this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(updatedStandardOptions); + return; + } + + if (!DataSourceOptionsAreEqual(this.ChatThread.DataSourceOptions, previousStandardOptions)) + return; + + await this.SetCurrentDataSourceOptions(updatedStandardOptions); + this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(updatedStandardOptions, this.ChatThread.AISelectedDataSources); + await this.ChatThreadChanged.InvokeAsync(this.ChatThread); + } + + private static bool DataSourceOptionsAreEqual(DataSourceOptions left, DataSourceOptions right) + { + return left.DisableDataSources == right.DisableDataSources + && left.AutomaticDataSourceSelection == right.AutomaticDataSourceSelection + && left.AutomaticValidation == right.AutomaticValidation + && left.PreselectedDataSourceIds.ToHashSet(StringComparer.Ordinal).SetEquals(right.PreselectedDataSourceIds); + } private string ExtractThreadName(string firstUserInput) { @@ -547,6 +580,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable if (!this.ComposerState.HasUserDraft && previousChatTemplate != this.currentChatTemplate) this.ComposerState.ApplyTemplate(this.currentChatTemplate); + + await this.ApplyUpdatedStandardDataSourceOptionsAfterConfigurationChange(); } private IReadOnlyList GetAgentSelectedDataSources() diff --git a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs index c95dc554..7f11972a 100644 --- a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs +++ b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs @@ -52,6 +52,7 @@ public partial class DataSourceSelection : MSGComponentBase private bool aiBasedSourceSelection; private bool aiBasedValidation; private bool areDataSourcesEnabled; + private uint loadAndApplyFiltersGeneration; #region Overrides of ComponentBase @@ -75,15 +76,7 @@ public partial class DataSourceSelection : MSGComponentBase // Right before the preselection would be used to kick off the // RAG process, we will filter the data sources as well. // - var preselectedSources = new List(this.DataSourceOptions.PreselectedDataSourceIds.Count); - foreach (var preselectedDataSourceId in this.DataSourceOptions.PreselectedDataSourceIds) - { - var dataSource = this.SettingsManager.ConfigurationData.DataSources.FirstOrDefault(ds => ds.Id == preselectedDataSourceId); - if (dataSource is not null) - preselectedSources.Add(dataSource); - } - - this.selectedDataSources = preselectedSources; + this.selectedDataSources = this.GetDataSourcesFromConfiguredIds(); await base.OnInitializedAsync(); } @@ -94,7 +87,7 @@ public partial class DataSourceSelection : MSGComponentBase this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection; this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation; this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources; - this.selectedDataSources = this.SettingsManager.ConfigurationData.DataSources.Where(ds => this.DataSourceOptions.PreselectedDataSourceIds.Contains(ds.Id)).ToList(); + this.selectedDataSources = this.GetDataSourcesFromConfiguredIds(); } switch (this.SelectionMode) @@ -120,7 +113,7 @@ public partial class DataSourceSelection : MSGComponentBase // In configuration mode, we have to load all data sources: // case DataSourceSelectionMode.CONFIGURATION_MODE: - this.availableDataSources = this.SettingsManager.ConfigurationData.DataSources; + this.availableDataSources = this.GetConfiguredDataSourcesSnapshot(); break; } @@ -157,7 +150,7 @@ public partial class DataSourceSelection : MSGComponentBase this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection; this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation; this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources; - this.selectedDataSources = this.SettingsManager.ConfigurationData.DataSources.Where(ds => this.DataSourceOptions.PreselectedDataSourceIds.Contains(ds.Id)).ToList(); + this.selectedDataSources = this.GetDataSourcesFromConfiguredIds(); this.waitingForDataSources = false; // @@ -177,20 +170,38 @@ public partial class DataSourceSelection : MSGComponentBase this.showDataSourceSelection = false; this.StateHasChanged(); } + + private IReadOnlyList GetConfiguredDataSourcesSnapshot() => this.SettingsManager.ConfigurationData.DataSources.ToList(); + + private IReadOnlyCollection GetDataSourcesFromConfiguredIds() + { + var preselectedDataSourceIds = this.DataSourceOptions.PreselectedDataSourceIds.ToHashSet(StringComparer.Ordinal); + return this.GetConfiguredDataSourcesSnapshot().Where(ds => preselectedDataSourceIds.Contains(ds.Id)).ToList(); + } private async Task LoadAndApplyFilters() { if(this.DataSourceOptions.DisableDataSources) + { + this.loadAndApplyFiltersGeneration++; return; + } if(this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE) + { + this.loadAndApplyFiltersGeneration++; return; + } + var generation = ++this.loadAndApplyFiltersGeneration; this.waitingForDataSources = true; this.StateHasChanged(); // Load the data sources: var sources = await this.DataSourceService.GetDataSources(this.LLMProvider, this.selectedDataSources); + if (generation != this.loadAndApplyFiltersGeneration) + return; + this.availableDataSources = sources.AllowedDataSources; this.selectedDataSources = sources.SelectedDataSources; this.waitingForDataSources = false; @@ -262,6 +273,7 @@ public partial class DataSourceSelection : MSGComponentBase private async Task OptionsChanged() { this.internalChange = true; + this.loadAndApplyFiltersGeneration++; await this.DataSourceOptionsChanged.InvokeAsync(this.DataSourceOptions); diff --git a/app/MindWork AI Studio/Tools/Services/DataSourceService.cs b/app/MindWork AI Studio/Tools/Services/DataSourceService.cs index dbd8954a..d4fcd838 100644 --- a/app/MindWork AI Studio/Tools/Services/DataSourceService.cs +++ b/app/MindWork AI Studio/Tools/Services/DataSourceService.cs @@ -70,9 +70,10 @@ public sealed class DataSourceService private async Task GetDataSources(bool usingTrustedProvider, IReadOnlyCollection? previousSelectedDataSources = null) { - var allDataSources = this.settingsManager.ConfigurationData.DataSources; + var allDataSources = this.settingsManager.ConfigurationData.DataSources.ToList(); + var previousSelectedDataSourceIds = previousSelectedDataSources?.Select(source => source.Id).ToHashSet(StringComparer.Ordinal) ?? []; var filteredDataSources = new List(allDataSources.Count); - var filteredSelectedDataSources = new List(previousSelectedDataSources?.Count ?? 0); + var filteredSelectedDataSources = new List(previousSelectedDataSourceIds.Count); var tasks = new List>(allDataSources.Count); // Start all checks in parallel: @@ -86,7 +87,7 @@ public sealed class DataSourceService if (source is not null) { filteredDataSources.Add(source); - if (previousSelectedDataSources is not null && previousSelectedDataSources.Contains(source)) + if (previousSelectedDataSourceIds.Contains(source.Id)) filteredSelectedDataSources.Add(source); } } @@ -205,4 +206,4 @@ public sealed class DataSourceService return null; } } -} \ No newline at end of file +}