diff --git a/app/MindWork AI Studio/Chat/ChatThread.cs b/app/MindWork AI Studio/Chat/ChatThread.cs
index 3e41161c..7709db7e 100644
--- a/app/MindWork AI Studio/Chat/ChatThread.cs
+++ b/app/MindWork AI Studio/Chat/ChatThread.cs
@@ -1,3 +1,4 @@
+using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
@@ -33,6 +34,11 @@ public sealed record ChatThread
///
public DataSourceOptions DataSourceOptions { get; set; } = new();
+ ///
+ /// The AI-selected data sources for this chat thread.
+ ///
+ public IReadOnlyList AISelectedDataSources { get; set; } = [];
+
///
/// The name of the chat thread. Usually generated by an AI model or manually edited by the user.
///
diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs
index 8275c245..c3b4625a 100644
--- a/app/MindWork AI Studio/Chat/ContentText.cs
+++ b/app/MindWork AI Studio/Chat/ContentText.cs
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using AIStudio.Agents;
+using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.Services;
@@ -74,6 +75,7 @@ public sealed class ContentText : IContent
var selectionAgent = Program.SERVICE_PROVIDER.GetService()!;
// Let the AI agent do its work:
+ IReadOnlyList finalAISelection = [];
var aiSelectedDataSources = await selectionAgent.PerformSelectionAsync(provider, lastPrompt, chatThread, dataSources, token);
// Check if the AI selected any data sources:
@@ -81,6 +83,11 @@ public sealed class ContentText : IContent
{
logger.LogWarning("The AI did not select any data sources. The RAG process is skipped.");
proceedWithRAG = false;
+
+ // Send the selected data sources to the data source selection component.
+ // Then, the user can see which data sources were selected by the AI.
+ await MessageBus.INSTANCE.SendMessage(null, Event.RAG_AUTO_DATA_SOURCES_SELECTED, finalAISelection);
+ chatThread.AISelectedDataSources = finalAISelection;
}
else
{
@@ -96,6 +103,9 @@ public sealed class ContentText : IContent
// Filter out the data sources that are not available:
aiSelectedDataSources = aiSelectedDataSources.Where(x => settings.ConfigurationData.DataSources.FirstOrDefault(ds => ds.Id == x.Id) is not null).ToList();
+ // Store the real AI-selected data sources:
+ finalAISelection = aiSelectedDataSources.Select(x => new DataSourceAgentSelected { DataSource = settings.ConfigurationData.DataSources.First(ds => ds.Id == x.Id), AIDecision = x, Selected = false }).ToList();
+
var numHallucinatedSources = totalAISelectedDataSources - aiSelectedDataSources.Count;
if(numHallucinatedSources > 0)
logger.LogWarning($"The AI hallucinated {numHallucinatedSources} data source(s). We ignore them.");
@@ -142,6 +152,10 @@ public sealed class ContentText : IContent
// Filter the data sources by the threshold:
//
aiSelectedDataSources = aiSelectedDataSources.Where(x => x.Confidence >= threshold).ToList();
+ foreach (var dataSource in finalAISelection)
+ if(aiSelectedDataSources.Any(x => x.Id == dataSource.DataSource.Id))
+ dataSource.Selected = true;
+
logger.LogInformation($"The AI selected {aiSelectedDataSources.Count} data source(s) with a confidence of at least {threshold}.");
// Transform the final data sources to the actual data sources:
@@ -151,12 +165,18 @@ public sealed class ContentText : IContent
// We have max. 3 data sources. We take all of them:
else
{
- // Transform the selected data sources to the actual data sources.
+ // Transform the selected data sources to the actual data sources:
selectedDataSources = aiSelectedDataSources.Select(x => settings.ConfigurationData.DataSources.FirstOrDefault(ds => ds.Id == x.Id)).Where(ds => ds is not null).ToList()!;
+
+ // Mark the data sources as selected:
+ foreach (var dataSource in finalAISelection)
+ dataSource.Selected = true;
}
- // Store the changes in the chat thread:
- chatThread.DataSourceOptions.PreselectedDataSourceIds = selectedDataSources.Select(ds => ds.Id).ToList();
+ // Send the selected data sources to the data source selection component.
+ // Then, the user can see which data sources were selected by the AI.
+ await MessageBus.INSTANCE.SendMessage(null, Event.RAG_AUTO_DATA_SOURCES_SELECTED, finalAISelection);
+ chatThread.AISelectedDataSources = finalAISelection;
}
}
else
diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor
index 2ae6eb2a..06dd07b2 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor
@@ -111,7 +111,7 @@
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
-
+
}
diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
index 78721864..7a25680b 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
@@ -305,6 +305,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
}
+ private IReadOnlyList GetAgentSelectedDataSources()
+ {
+ if (this.ChatThread is null)
+ return [];
+
+ return this.ChatThread.AISelectedDataSources;
+ }
+
private DataSourceOptions GetCurrentDataSourceOptions()
{
if (this.ChatThread is not null)
@@ -482,12 +490,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
// Disable the stream state:
this.isStreaming = false;
- // Update the data source options. This is useful when
- // the AI is responsible for selecting the data source.
- // The user can then see the selected data source:
- if(this.ChatThread?.DataSourceOptions.AutomaticDataSourceSelection ?? false)
- this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread!.DataSourceOptions);
-
// Update the UI:
this.StateHasChanged();
}
@@ -682,7 +684,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.ChatThread.WorkspaceId);
this.WorkspaceName(this.currentWorkspaceName);
- this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread.DataSourceOptions);
+ this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread.DataSourceOptions, this.ChatThread.AISelectedDataSources);
}
else
{
diff --git a/app/MindWork AI Studio/Components/DataSourceAgentSelected.cs b/app/MindWork AI Studio/Components/DataSourceAgentSelected.cs
new file mode 100644
index 00000000..5108596b
--- /dev/null
+++ b/app/MindWork AI Studio/Components/DataSourceAgentSelected.cs
@@ -0,0 +1,25 @@
+using AIStudio.Agents;
+using AIStudio.Settings;
+
+namespace AIStudio.Components;
+
+///
+/// A data structure to combine the data source and the underlying AI decision.
+///
+public sealed class DataSourceAgentSelected
+{
+ ///
+ /// The data source.
+ ///
+ public required IDataSource DataSource { get; set; }
+
+ ///
+ /// The AI decision, which led to the selection of the data source.
+ ///
+ public required SelectedDataSource AIDecision { get; set; }
+
+ ///
+ /// Indicates whether the data source is part of the final selection for the RAG process.
+ ///
+ public bool Selected { get; set; }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/DataSourceSelection.razor b/app/MindWork AI Studio/Components/DataSourceSelection.razor
index f9dff17b..08160443 100644
--- a/app/MindWork AI Studio/Components/DataSourceSelection.razor
+++ b/app/MindWork AI Studio/Components/DataSourceSelection.razor
@@ -24,7 +24,7 @@
Data Source Selection
-
+
@if (this.waitingForDataSources)
{
@@ -38,16 +38,54 @@
{
-
- this.SelectionChanged(x))" Style="max-height: 14em;">
- @foreach (var source in this.availableDataSources)
- {
-
- @source.Name
-
- }
-
-
+
+ @if (this.aiBasedSourceSelection is false || this.DataSourcesAISelected.Count == 0)
+ {
+
+ this.SelectionChanged(x))" Style="max-height: 14em;">
+ @foreach (var source in this.availableDataSources)
+ {
+
+ @source.Name
+
+ }
+
+
+ }
+ else
+ {
+
+
+
+ @foreach (var source in this.availableDataSources)
+ {
+
+ @source.Name
+
+ }
+
+
+
+
+ @foreach (var source in this.DataSourcesAISelected)
+ {
+
+
+
+ @source.DataSource.Name
+
+
+
+
+ @this.GetAIReasoning(source)
+
+
+
+ }
+
+
+
+ }
}
}
@@ -79,7 +117,7 @@ else if (this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE)
- this.SelectionChanged(x))">
+ this.SelectionChanged(x))">
@foreach (var source in this.availableDataSources)
{
diff --git a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs
index 7b75bbd4..73796d6e 100644
--- a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs
+++ b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs
@@ -25,6 +25,9 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I
[Parameter]
public EventCallback DataSourceOptionsChanged { get; set; }
+
+ [Parameter]
+ public IReadOnlyList DataSourcesAISelected { get; set; } = [];
[Parameter]
public string ConfigurationHeaderMessage { get; set; } = string.Empty;
@@ -58,7 +61,7 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I
protected override async Task OnInitializedAsync()
{
this.MessageBus.RegisterComponent(this);
- this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]);
+ this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED, Event.RAG_AUTO_DATA_SOURCES_SELECTED ]);
//
// Load the settings:
@@ -129,9 +132,17 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I
#endregion
- public void ChangeOptionWithoutSaving(DataSourceOptions options)
+ private SelectionMode GetListSelectionMode() => this.aiBasedSourceSelection ? MudBlazor.SelectionMode.SingleSelection : MudBlazor.SelectionMode.MultiSelection;
+
+ private IReadOnlyCollection GetSelectedDataSourcesWithAI() => this.DataSourcesAISelected.Where(n => n.Selected).ToList();
+
+ private string GetAIReasoning(DataSourceAgentSelected source) => $"AI reasoning (confidence {source.AIDecision.Confidence:P0}): {source.AIDecision.Reason}";
+
+ public void ChangeOptionWithoutSaving(DataSourceOptions options, IReadOnlyList? aiSelectedDataSources = null)
{
this.DataSourceOptions = options;
+ this.DataSourcesAISelected = aiSelectedDataSources ?? [];
+
this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection;
this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation;
this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources;
@@ -237,6 +248,13 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I
this.showDataSourceSelection = false;
this.StateHasChanged();
break;
+
+ case Event.RAG_AUTO_DATA_SOURCES_SELECTED:
+ if(data is IReadOnlyList aiSelectedDataSources)
+ this.DataSourcesAISelected = aiSelectedDataSources;
+
+ this.StateHasChanged();
+ break;
}
return Task.CompletedTask;
diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs
index 37a855fd..45005fd1 100644
--- a/app/MindWork AI Studio/Tools/Event.cs
+++ b/app/MindWork AI Studio/Tools/Event.cs
@@ -23,6 +23,9 @@ public enum Event
WORKSPACE_LOADED_CHAT_CHANGED,
WORKSPACE_TOGGLE_OVERLAY,
+ // RAG events:
+ RAG_AUTO_DATA_SOURCES_SELECTED,
+
// Send events:
SEND_TO_GRAMMAR_SPELLING_ASSISTANT,
SEND_TO_ICON_FINDER_ASSISTANT,