mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-16 06:13:48 +00:00
Integrated data sources into any chat (#282)
This commit is contained in:
1 parent
954cf44939
commit
f4780939fc
70 files changed
+1506
-250
No files matched your search
@@ -97,7 +97,7 @@
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
||||
{
|
||||
<ConfidenceInfo Mode="ConfidenceInfoMode.ICON" LLMProvider="@this.Provider.UsedLLMProvider"/>
|
||||
<ConfidenceInfo Mode="PopoverTriggerMode.ICON" LLMProvider="@this.Provider.UsedLLMProvider"/>
|
||||
}
|
||||
|
||||
@if (this.isStreaming && this.cancellationTokenSource is not null)
|
||||
@@ -108,6 +108,11 @@
|
||||
}
|
||||
|
||||
<ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged"/>
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<DataSourceSelection @ref="@this.dataSourceSelectionComponent" PopoverTriggerMode="PopoverTriggerMode.BUTTON" PopoverButtonClasses="ma-3" LLMProvider="@this.Provider" DataSourceOptions="@this.GetCurrentDataSourceOptions()" DataSourceOptionsChanged="@(async options => await this.SetCurrentDataSourceOptions(options))"/>
|
||||
}
|
||||
</MudToolBar>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
@@ -3,6 +3,7 @@ using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
@@ -40,9 +41,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private DataSourceService DataSourceService { get; init; } = null!;
|
||||
|
||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
private DataSourceSelection? dataSourceSelectionComponent;
|
||||
private DataSourceOptions earlyDataSourceOptions = new();
|
||||
private Profile currentProfile = Profile.NO_PROFILE;
|
||||
private bool hasUnsavedChanges;
|
||||
private bool mustScrollToBottomAfterRender;
|
||||
@@ -66,21 +72,40 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Apply the filters for the message bus:
|
||||
this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED ]);
|
||||
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
|
||||
// Get the preselected profile:
|
||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
|
||||
|
||||
//
|
||||
// Check for deferred messages of the kind 'SEND_TO_CHAT',
|
||||
// aka the user sends an assistant result to the chat:
|
||||
//
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<ChatThread>(Event.SEND_TO_CHAT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
{
|
||||
//
|
||||
// Yes, the user sent an assistant result to the chat.
|
||||
//
|
||||
|
||||
// Use chat thread sent by the user:
|
||||
this.ChatThread = deferredContent;
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread.Name}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
|
||||
// We know already that the chat thread is not null,
|
||||
// but we have to check it again for the nullability
|
||||
// for the compiler:
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
//
|
||||
// Check if the chat thread has a name. If not, we
|
||||
// generate the name now:
|
||||
//
|
||||
if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
|
||||
{
|
||||
var firstUserBlock = this.ChatThread.Blocks.FirstOrDefault(x => x.Role == ChatRole.USER);
|
||||
@@ -94,12 +119,24 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the user wants to apply the standard chat data source options:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior is SendToChatDataSourceBehavior.APPLY_STANDARD_CHAT_DATA_SOURCE_OPTIONS)
|
||||
this.ChatThread.DataSourceOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy();
|
||||
|
||||
//
|
||||
// Check if the user wants to store the chat automatically:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
this.autoSaveEnabled = true;
|
||||
this.mustStoreChat = true;
|
||||
|
||||
// Ensure the workspace exists:
|
||||
//
|
||||
// When a standard workspace is used, we have to ensure
|
||||
// that the workspace is available:
|
||||
//
|
||||
if(this.ChatThread.WorkspaceId == KnownWorkspaces.ERI_SERVER_WORKSPACE_ID)
|
||||
await WorkspaceBehaviour.EnsureERIServerWorkspace();
|
||||
|
||||
@@ -108,14 +145,38 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// No, the user did not send an assistant result to the chat.
|
||||
//
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the user wants to show the latest message after loading:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)
|
||||
{
|
||||
//
|
||||
// We cannot scroll to the bottom right now because the
|
||||
// chat component is not rendered yet. We have to wait for
|
||||
// the rendering process to finish. Thus, we set a flag
|
||||
// to scroll to the bottom after the rendering process.:
|
||||
//
|
||||
this.mustScrollToBottomAfterRender = true;
|
||||
this.scrollRenderCountdown = 4;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if another component deferred the loading of a chat.
|
||||
//
|
||||
// This is used, e.g., for the bias-of-the-day component:
|
||||
// when the bias for this day was already produced, the bias
|
||||
// component sends a message to the chat component to load
|
||||
// the chat with the bias:
|
||||
//
|
||||
var deferredLoading = MessageBus.INSTANCE.CheckDeferredMessages<LoadChat>(Event.LOAD_CHAT).FirstOrDefault();
|
||||
if (deferredLoading != default)
|
||||
{
|
||||
@@ -124,6 +185,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.Logger.LogInformation($"The loading of the chat '{this.loadChat.ChatId}' was deferred and will be loaded now.");
|
||||
}
|
||||
|
||||
//
|
||||
// When for whatever reason we have a chat thread, we have to
|
||||
// ensure that the corresponding workspace id is set and the
|
||||
// workspace name is loaded:
|
||||
//
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
@@ -131,6 +197,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
}
|
||||
|
||||
// Select the correct provider:
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
@@ -204,6 +271,13 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
private string UserInputClass => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? "confidence-border" : string.Empty;
|
||||
|
||||
private void ApplyStandardDataSourceOptions()
|
||||
{
|
||||
var chatDefaultOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy();
|
||||
this.earlyDataSourceOptions = chatDefaultOptions;
|
||||
this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(chatDefaultOptions);
|
||||
}
|
||||
|
||||
private string ExtractThreadName(string firstUserInput)
|
||||
{
|
||||
// We select the first 10 words of the user input:
|
||||
@@ -231,8 +305,35 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
private DataSourceOptions GetCurrentDataSourceOptions()
|
||||
{
|
||||
if (this.ChatThread is not null)
|
||||
return this.ChatThread.DataSourceOptions;
|
||||
|
||||
return this.earlyDataSourceOptions;
|
||||
}
|
||||
|
||||
private async Task SetCurrentDataSourceOptions(DataSourceOptions updatedOptions)
|
||||
{
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.hasUnsavedChanges = true;
|
||||
this.ChatThread.DataSourceOptions = updatedOptions;
|
||||
if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
await this.SaveThread();
|
||||
this.hasUnsavedChanges = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
this.earlyDataSourceOptions = updatedOptions;
|
||||
}
|
||||
|
||||
private async Task InputKeyEvent(KeyboardEventArgs keyEvent)
|
||||
{
|
||||
if(this.dataSourceSelectionComponent?.IsVisible ?? false)
|
||||
this.dataSourceSelectionComponent.Hide();
|
||||
|
||||
this.hasUnsavedChanges = true;
|
||||
var key = keyEvent.Code.ToLowerInvariant();
|
||||
|
||||
@@ -276,6 +377,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
WorkspaceId = this.currentWorkspaceId,
|
||||
ChatId = Guid.NewGuid(),
|
||||
DataSourceOptions = this.earlyDataSourceOptions,
|
||||
Name = this.ExtractThreadName(this.userInput),
|
||||
Seed = this.RNG.Next(),
|
||||
Blocks = [],
|
||||
@@ -365,7 +467,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.Provider.CreateProvider(this.Logger), this.SettingsManager, this.Provider.Model, lastUserPrompt, this.ChatThread, this.cancellationTokenSource.Token);
|
||||
await aiText.CreateFromProviderAsync(this.Provider.CreateProvider(this.Logger), this.SettingsManager, this.DataSourceService, this.Provider.Model, lastUserPrompt, this.ChatThread, this.cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
this.cancellationTokenSource = null;
|
||||
@@ -412,6 +514,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
private async Task StartNewChat(bool useSameWorkspace = false, bool deletePreviousChat = false)
|
||||
{
|
||||
//
|
||||
// Want the user to manage the chat storage manually? In that case, we have to ask the user
|
||||
// about possible data loss:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges)
|
||||
{
|
||||
var dialogParameters = new DialogParameters
|
||||
@@ -425,6 +531,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Delete the previous chat when desired and necessary:
|
||||
//
|
||||
if (this.ChatThread is not null && deletePreviousChat)
|
||||
{
|
||||
string chatPath;
|
||||
@@ -439,10 +548,16 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await this.Workspaces.DeleteChat(chatPath, askForConfirmation: false, unloadChat: true);
|
||||
}
|
||||
|
||||
//
|
||||
// Reset our state:
|
||||
//
|
||||
this.isStreaming = false;
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
|
||||
//
|
||||
// Reset the LLM provider considering the user's settings:
|
||||
//
|
||||
switch (this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior)
|
||||
{
|
||||
case AddChatProviderBehavior.ADDED_CHATS_USE_DEFAULT_PROVIDER:
|
||||
@@ -461,8 +576,16 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Reset the chat thread or create a new one:
|
||||
//
|
||||
if (!useSameWorkspace)
|
||||
{
|
||||
//
|
||||
// When the user wants to start a new chat outside the current workspace,
|
||||
// we have to reset the workspace id and the workspace name. Also, we have
|
||||
// to reset the chat thread:
|
||||
//
|
||||
this.ChatThread = null;
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
@@ -470,6 +593,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// When the user wants to start a new chat in the same workspace, we have to
|
||||
// reset the chat thread only. The workspace id and the workspace name remain
|
||||
// the same:
|
||||
//
|
||||
this.ChatThread = new()
|
||||
{
|
||||
SelectedProvider = this.Provider.Id,
|
||||
@@ -482,8 +610,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
Blocks = [],
|
||||
};
|
||||
}
|
||||
|
||||
this.userInput = string.Empty;
|
||||
|
||||
// Now, we have to reset the data source options as well:
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
|
||||
// Notify the parent component about the change:
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
@@ -543,12 +674,14 @@ 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
}
|
||||
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
@@ -572,6 +705,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
|
||||
this.ChatThread = null;
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@using AIStudio.Provider
|
||||
<div class="d-flex">
|
||||
<MudTooltip Text="Shows and hides the confidence card with information about the selected LLM provider." Placement="Placement.Top">
|
||||
@if (this.Mode is ConfidenceInfoMode.ICON)
|
||||
@if (this.Mode is PopoverTriggerMode.ICON)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Security" Class="confidence-icon" Style="@this.LLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager)" OnClick="@(() => this.ToggleConfidence())"/>
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
<MudText Typo="Typo.h5">Confidence Card</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudCardContent Style="max-height: 50vh; overflow: auto;">
|
||||
<MudText Typo="Typo.h6">Description</MudText>
|
||||
<MudMarkdown Value="@this.currentConfidence.Description"/>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace AIStudio.Components;
|
||||
public partial class ConfidenceInfo : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{
|
||||
[Parameter]
|
||||
public ConfidenceInfoMode Mode { get; set; } = ConfidenceInfoMode.BUTTON;
|
||||
public PopoverTriggerMode Mode { get; set; } = PopoverTriggerMode.BUTTON;
|
||||
|
||||
[Parameter]
|
||||
public LLMProviders LLMProvider { get; set; }
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
@using AIStudio.Settings
|
||||
|
||||
@if (this.SelectionMode is DataSourceSelectionMode.SELECTION_MODE)
|
||||
{
|
||||
<div class="d-flex">
|
||||
<MudTooltip Text="Select the data sources you want to use here." Placement="Placement.Top">
|
||||
@if (this.PopoverTriggerMode is PopoverTriggerMode.ICON)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Source" Class="@this.PopoverButtonClasses" OnClick="@(() => this.ToggleDataSourceSelection())"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Source" Class="@this.PopoverButtonClasses" OnClick="@(() => this.ToggleDataSourceSelection())">
|
||||
Select data sources
|
||||
</MudButton>
|
||||
}
|
||||
</MudTooltip>
|
||||
|
||||
<MudPopover Open="@this.showDataSourceSelection" AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" DropShadow="@true" Class="border-solid border-4 rounded-lg">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<PreviewPrototype/>
|
||||
<MudText Typo="Typo.h5">Data Source Selection</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Style="max-height: 60vh; overflow: auto;">
|
||||
@if (this.waitingForDataSources)
|
||||
{
|
||||
<MudSkeleton Width="30%" Height="42px;"/>
|
||||
<MudSkeleton Width="80%"/>
|
||||
<MudSkeleton Width="100%"/>
|
||||
}
|
||||
else if (this.showDataSourceSelection)
|
||||
{
|
||||
<MudTextSwitch Label="Are data sources enabled?" Value="@this.areDataSourcesEnabled" LabelOn="Yes, I want to use data sources." LabelOff="No, I don't want to use data sources." ValueChanged="@this.EnabledChanged"/>
|
||||
@if (this.areDataSourcesEnabled)
|
||||
{
|
||||
<MudTextSwitch Label="AI-based data source selection" Value="@this.aiBasedSourceSelection" LabelOn="Yes, let the AI decide which data sources are needed." LabelOff="No, I manually decide which data source to use." ValueChanged="@this.AutoModeChanged"/>
|
||||
<MudTextSwitch Label="AI-based data validation" Value="@this.aiBasedValidation" LabelOn="Yes, let the AI validate & filter the retrieved data." LabelOff="No, use all data retrieved from the data sources." ValueChanged="@this.ValidationModeChanged"/>
|
||||
<MudField Label="Available Data Sources" Variant="Variant.Outlined" Class="mb-3" Disabled="@this.aiBasedSourceSelection">
|
||||
<MudList T="IDataSource" SelectionMode="MudBlazor.SelectionMode.MultiSelection" @bind-SelectedValues:get="@this.selectedDataSources" @bind-SelectedValues:set="@(x => this.SelectionChanged(x))" Style="max-height: 14em;">
|
||||
@foreach (var source in this.availableDataSources)
|
||||
{
|
||||
<MudListItem Value="@source">
|
||||
@source.Name
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudField>
|
||||
}
|
||||
}
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Filled" OnClick="@(() => this.HideDataSourceSelection())">
|
||||
Close
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudPopover>
|
||||
</div>
|
||||
}
|
||||
else if (this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-8 mt-3 border-dashed border rounded-lg">
|
||||
<PreviewPrototype/>
|
||||
<MudText Typo="Typo.h5">Data Source Selection</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(this.ConfigurationHeaderMessage))
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@this.ConfigurationHeaderMessage
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudTextSwitch Label="Are data sources enabled?" Value="@this.areDataSourcesEnabled" LabelOn="Yes, I want to use data sources." LabelOff="No, I don't want to use data sources." ValueChanged="@this.EnabledChanged"/>
|
||||
@if (this.areDataSourcesEnabled)
|
||||
{
|
||||
<MudTextSwitch Label="AI-based data source selection" Value="@this.aiBasedSourceSelection" LabelOn="Yes, let the AI decide which data sources are needed." LabelOff="No, I manually decide which data source to use." ValueChanged="@this.AutoModeChanged"/>
|
||||
<MudTextSwitch Label="AI-based data validation" Value="@this.aiBasedValidation" LabelOn="Yes, let the AI validate & filter the retrieved data." LabelOff="No, use all data retrieved from the data sources." ValueChanged="@this.ValidationModeChanged"/>
|
||||
<MudField Label="Available Data Sources" Variant="Variant.Outlined" Class="mb-3" Disabled="@this.aiBasedSourceSelection">
|
||||
<MudList T="IDataSource" SelectionMode="MudBlazor.SelectionMode.MultiSelection" @bind-SelectedValues:get="@this.selectedDataSources" @bind-SelectedValues:set="@(x => this.SelectionChanged(x))">
|
||||
@foreach (var source in this.availableDataSources)
|
||||
{
|
||||
<MudListItem Value="@source">
|
||||
@source.Name
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudField>
|
||||
}
|
||||
</MudPaper>
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{
|
||||
[Parameter]
|
||||
public DataSourceSelectionMode SelectionMode { get; set; } = DataSourceSelectionMode.SELECTION_MODE;
|
||||
|
||||
[Parameter]
|
||||
public PopoverTriggerMode PopoverTriggerMode { get; set; } = PopoverTriggerMode.BUTTON;
|
||||
|
||||
[Parameter]
|
||||
public string PopoverButtonClasses { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public required AIStudio.Settings.Provider LLMProvider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public required DataSourceOptions DataSourceOptions { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<DataSourceOptions> DataSourceOptionsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ConfigurationHeaderMessage { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public bool AutoSaveAppSettings { get; set; }
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private DataSourceService DataSourceService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<DataSourceSelection> Logger { get; init; } = null!;
|
||||
|
||||
private bool internalChange;
|
||||
private bool showDataSourceSelection;
|
||||
private bool waitingForDataSources = true;
|
||||
private IReadOnlyList<IDataSource> availableDataSources = [];
|
||||
private IReadOnlyCollection<IDataSource> selectedDataSources = [];
|
||||
private bool aiBasedSourceSelection;
|
||||
private bool aiBasedValidation;
|
||||
private bool areDataSourcesEnabled;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MessageBus.RegisterComponent(this);
|
||||
this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]);
|
||||
|
||||
//
|
||||
// Load the settings:
|
||||
//
|
||||
this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection;
|
||||
this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation;
|
||||
this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources;
|
||||
this.waitingForDataSources = this.areDataSourcesEnabled;
|
||||
|
||||
//
|
||||
// Preselect the data sources. Right now, we cannot filter
|
||||
// the data sources. Later, when the component is shown, we
|
||||
// will filter the data sources.
|
||||
//
|
||||
// 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<IDataSource>(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;
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!this.internalChange)
|
||||
{
|
||||
this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection;
|
||||
this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation;
|
||||
this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources;
|
||||
}
|
||||
|
||||
switch (this.SelectionMode)
|
||||
{
|
||||
//
|
||||
// In selection mode, we have to load & filter the data sources
|
||||
// when the component is shown:
|
||||
//
|
||||
case DataSourceSelectionMode.SELECTION_MODE:
|
||||
|
||||
//
|
||||
// For external changes, we have to reload & filter
|
||||
// the data sources:
|
||||
//
|
||||
if (this.showDataSourceSelection && !this.internalChange)
|
||||
await this.LoadAndApplyFilters();
|
||||
else
|
||||
this.internalChange = false;
|
||||
|
||||
break;
|
||||
|
||||
//
|
||||
// In configuration mode, we have to load all data sources:
|
||||
//
|
||||
case DataSourceSelectionMode.CONFIGURATION_MODE:
|
||||
this.availableDataSources = this.SettingsManager.ConfigurationData.DataSources;
|
||||
break;
|
||||
}
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ChangeOptionWithoutSaving(DataSourceOptions options)
|
||||
{
|
||||
this.DataSourceOptions = options;
|
||||
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.waitingForDataSources = false;
|
||||
|
||||
//
|
||||
// Remark: We do not apply the filters here. This is done later
|
||||
// when either the parameters are changed or just before the
|
||||
// RAG process is started (outside of this component).
|
||||
//
|
||||
// In fact, when we apply the filters here, multiple calls
|
||||
// to the filter method would be made. We would get conflicts.
|
||||
//
|
||||
}
|
||||
|
||||
public bool IsVisible => this.showDataSourceSelection;
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
this.showDataSourceSelection = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task LoadAndApplyFilters()
|
||||
{
|
||||
if(this.DataSourceOptions.DisableDataSources)
|
||||
return;
|
||||
|
||||
this.waitingForDataSources = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Load the data sources:
|
||||
var sources = await this.DataSourceService.GetDataSources(this.LLMProvider, this.selectedDataSources);
|
||||
this.availableDataSources = sources.AllowedDataSources;
|
||||
this.selectedDataSources = sources.SelectedDataSources;
|
||||
this.waitingForDataSources = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task EnabledChanged(bool state)
|
||||
{
|
||||
this.areDataSourcesEnabled = state;
|
||||
this.DataSourceOptions.DisableDataSources = !this.areDataSourcesEnabled;
|
||||
|
||||
await this.LoadAndApplyFilters();
|
||||
await this.OptionsChanged();
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AutoModeChanged(bool state)
|
||||
{
|
||||
this.aiBasedSourceSelection = state;
|
||||
this.DataSourceOptions.AutomaticDataSourceSelection = this.aiBasedSourceSelection;
|
||||
|
||||
await this.OptionsChanged();
|
||||
}
|
||||
|
||||
private async Task ValidationModeChanged(bool state)
|
||||
{
|
||||
this.aiBasedValidation = state;
|
||||
this.DataSourceOptions.AutomaticValidation = this.aiBasedValidation;
|
||||
|
||||
await this.OptionsChanged();
|
||||
}
|
||||
|
||||
private async Task SelectionChanged(IReadOnlyCollection<IDataSource>? chosenDataSources)
|
||||
{
|
||||
this.selectedDataSources = chosenDataSources ?? [];
|
||||
this.DataSourceOptions.PreselectedDataSourceIds = this.selectedDataSources.Select(ds => ds.Id).ToList();
|
||||
|
||||
await this.OptionsChanged();
|
||||
}
|
||||
|
||||
private async Task OptionsChanged()
|
||||
{
|
||||
this.internalChange = true;
|
||||
|
||||
await this.DataSourceOptionsChanged.InvokeAsync(this.DataSourceOptions);
|
||||
|
||||
if(this.AutoSaveAppSettings)
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
|
||||
private async Task ToggleDataSourceSelection()
|
||||
{
|
||||
this.showDataSourceSelection = !this.showDataSourceSelection;
|
||||
if (this.showDataSourceSelection)
|
||||
await this.LoadAndApplyFilters();
|
||||
}
|
||||
|
||||
private void HideDataSourceSelection() => this.showDataSourceSelection = false;
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(ConfidenceInfo);
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.COLOR_THEME_CHANGED:
|
||||
this.showDataSourceSelection = false;
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.MessageBus.Unregister(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public enum DataSourceSelectionMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The user is selecting data sources for, e.g., the chat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In this case, we have to filter the data sources based on the
|
||||
/// selected provider and check security requirements.
|
||||
/// </remarks>
|
||||
SELECTION_MODE,
|
||||
|
||||
/// <summary>
|
||||
/// The user is configuring the default data sources, e.g., for the chat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In this case, all data sources are available for selection.
|
||||
/// They get filtered later based on the selected provider and
|
||||
/// security requirements.
|
||||
/// </remarks>
|
||||
CONFIGURATION_MODE,
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public enum ConfidenceInfoMode
|
||||
public enum PopoverTriggerMode
|
||||
{
|
||||
BUTTON,
|
||||
ICON,
|
||||
@@ -14,11 +14,14 @@ public partial class ProfileSelection : ComponentBase
|
||||
|
||||
[Parameter]
|
||||
public string MarginLeft { get; set; } = "ml-3";
|
||||
|
||||
[Parameter]
|
||||
public string MarginRight { get; set; } = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
private string MarginClass => $"{this.MarginLeft}";
|
||||
private string MarginClass => $"{this.MarginLeft} {this.MarginRight}";
|
||||
|
||||
private async Task SelectionChanged(Profile profile)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Chat" HeaderText="Chat Options">
|
||||
@@ -12,4 +13,10 @@
|
||||
<ConfigurationProviderSelection Component="Components.CHAT" Data="@this.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for chats?"/>
|
||||
</MudPaper>
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<DataSourceSelection SelectionMode="DataSourceSelectionMode.CONFIGURATION_MODE" AutoSaveAppSettings="@true" @bind-DataSourceOptions="@this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions" ConfigurationHeaderMessage="You can set default data sources and options for new chats. You can change these settings later for each individual chat."/>
|
||||
<ConfigurationSelect OptionDescription="Apply default data source option when sending assistant results to chat" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior)" Data="@ConfigurationSelectDataFactory.GetSendToChatDataSourceBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior = selectedValue)" OptionHelp="Do you want to apply the default data source options when sending assistant results to chat?"/>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
Lines="3"
|
||||
MaxLines="@this.MaxLines"
|
||||
AutoGrow="@true"
|
||||
Style="@this.GetColor()"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES" />
|
||||
|
||||
@if (this.ShowingCopyButton)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -20,6 +21,9 @@ public partial class TextInfoLines : ComponentBase
|
||||
|
||||
[Parameter]
|
||||
public bool ShowingCopyButton { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public TextColor Color { get; set; } = TextColor.DEFAULT;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
@@ -47,4 +51,13 @@ public partial class TextInfoLines : ComponentBase
|
||||
private string ClipboardTooltip => $"Copy {this.ClipboardTooltipSubject} to the clipboard";
|
||||
|
||||
private async Task CopyToClipboard(string content) => await this.RustService.CopyText2Clipboard(this.Snackbar, content);
|
||||
|
||||
private string GetColor()
|
||||
{
|
||||
var htmlColorCode = this.Color.GetHTMLColor(this.SettingsManager);
|
||||
if(string.IsNullOrWhiteSpace(htmlColorCode))
|
||||
return string.Empty;
|
||||
|
||||
return $"color: {htmlColorCode} !important;";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user