Pick tools and data sources while editing a chat template

This commit is contained in:
Thorsten Sommer 2026-09-22 10:17:51 +02:00
parent e31da42901
commit af89a8c952
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 100 additions and 6 deletions

View File

@ -179,13 +179,13 @@ else if (this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE)
</MudText>
}
<MudTextSwitch Label="@T("Are data sources enabled?")" Value="@this.areDataSourcesEnabled" LabelOn="@T("Yes, I want to use data sources.")" LabelOff="@T("No, I don't want to use data sources.")" ValueChanged="@this.EnabledChanged" Disabled="@this.IsPreselectedDataSourcesDisabledLocked()"/>
<MudTextSwitch Label="@T("Are data sources enabled?")" Value="@this.areDataSourcesEnabled" LabelOn="@T("Yes, I want to use data sources.")" LabelOff="@T("No, I don't want to use data sources.")" ValueChanged="@this.EnabledChanged" Disabled="@(this.ReadOnly || this.IsPreselectedDataSourcesDisabledLocked())"/>
@if (this.areDataSourcesEnabled)
{
<MudTextSwitch Label="@T("AI-based data source selection")" Value="@this.aiBasedSourceSelection" LabelOn="@T("Yes, let the AI decide which data sources are needed.")" LabelOff="@T("No, I manually decide which data source to use.")" ValueChanged="@this.AutoModeChanged" Disabled="@this.IsPreselectedDataSourcesAutomaticSelectionLocked()"/>
<MudTextSwitch Label="@T("AI-based data validation")" Value="@this.aiBasedValidation" LabelOn="@T("Yes, let the AI validate & filter the retrieved data.")" LabelOff="@T("No, use all data retrieved from the data sources.")" ValueChanged="@this.ValidationModeChanged" Disabled="@this.IsPreselectedDataSourcesAutomaticValidationLocked()"/>
<MudField Label="@T("Available Data Sources")" Variant="Variant.Outlined" Class="mb-3" Disabled="@(this.aiBasedSourceSelection || this.IsPreselectedDataSourceIdsLocked())">
<MudList T="IDataSource" Dense="@true" Class="data-source-rows" SelectionMode="@this.GetListSelectionMode()" @bind-SelectedValues:get="@this.selectedDataSources" @bind-SelectedValues:set="@(x => this.SelectionChanged(x))" ReadOnly="@this.IsPreselectedDataSourceIdsLocked()">
<MudTextSwitch Label="@T("AI-based data source selection")" Value="@this.aiBasedSourceSelection" LabelOn="@T("Yes, let the AI decide which data sources are needed.")" LabelOff="@T("No, I manually decide which data source to use.")" ValueChanged="@this.AutoModeChanged" Disabled="@(this.ReadOnly || this.IsPreselectedDataSourcesAutomaticSelectionLocked())"/>
<MudTextSwitch Label="@T("AI-based data validation")" Value="@this.aiBasedValidation" LabelOn="@T("Yes, let the AI validate & filter the retrieved data.")" LabelOff="@T("No, use all data retrieved from the data sources.")" ValueChanged="@this.ValidationModeChanged" Disabled="@(this.ReadOnly || this.IsPreselectedDataSourcesAutomaticValidationLocked())"/>
<MudField Label="@T("Available Data Sources")" Variant="Variant.Outlined" Class="mb-3" Disabled="@(this.ReadOnly || this.aiBasedSourceSelection || this.IsPreselectedDataSourceIdsLocked())">
<MudList T="IDataSource" Dense="@true" Class="data-source-rows" SelectionMode="@this.GetListSelectionMode()" @bind-SelectedValues:get="@this.selectedDataSources" @bind-SelectedValues:set="@(x => this.SelectionChanged(x))" ReadOnly="@(this.ReadOnly || this.IsPreselectedDataSourceIdsLocked())">
@*
The configuration mode lists what was configured, without filtering it, so no
row here is ever waiting for an index.

View File

@ -39,6 +39,27 @@ public partial class DataSourceSelection : MSGComponentBase
[Parameter]
public bool AutoSaveAppSettings { get; set; }
/// <summary>
/// Shows the options without letting the user change them.
/// </summary>
/// <remarks>
/// For options somebody else decided on, such as those of a chat template an organization
/// rolled out. Seeing which data such a chat will search is the point; changing it here is not.
/// </remarks>
[Parameter]
public bool ReadOnly { get; set; }
/// <summary>
/// Whether the options edited here are the data source defaults of the chat.
/// </summary>
/// <remarks>
/// Those defaults can be locked by a configuration plugin, and this component reads the locks
/// from the chat settings. Wherever the same options belong to something else — to a chat
/// template, say — the locks of the chat defaults have nothing to say about them.
/// </remarks>
[Parameter]
public bool ConfiguresChatDefaults { get; set; } = true;
[Inject]
private DataSourceService DataSourceService { get; init; } = null!;
@ -334,6 +355,7 @@ public partial class DataSourceSelection : MSGComponentBase
private bool IsPreselectedDataSourcesDisabledLocked()
{
return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE
&& this.ConfiguresChatDefaults
&& ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourcesDisabled, out var meta)
&& meta.IsLocked;
}
@ -341,6 +363,7 @@ public partial class DataSourceSelection : MSGComponentBase
private bool IsPreselectedDataSourcesAutomaticSelectionLocked()
{
return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE
&& this.ConfiguresChatDefaults
&& ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourcesAutomaticSelection, out var meta)
&& meta.IsLocked;
}
@ -348,6 +371,7 @@ public partial class DataSourceSelection : MSGComponentBase
private bool IsPreselectedDataSourcesAutomaticValidationLocked()
{
return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE
&& this.ConfiguresChatDefaults
&& ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourcesAutomaticValidation, out var meta)
&& meta.IsLocked;
}
@ -355,6 +379,7 @@ public partial class DataSourceSelection : MSGComponentBase
private bool IsPreselectedDataSourceIdsLocked()
{
return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE
&& this.ConfiguresChatDefaults
&& ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourceIds, out var meta)
&& meta.IsLocked;
}

View File

@ -1,4 +1,5 @@
@using AIStudio.Chat
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase
<MudDialog>
@ -109,6 +110,35 @@
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.AllowProfileUsage" Color="Color.Primary" Label="@T("Allow the use of profiles together with this chat template?")" LabelOn="@T("Yes, allow profiles when using this template")" LabelOff="@T("No, prohibit profile use for this template")" Disabled="@this.IsReadOnly" />
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Tools")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says.")
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.preselectTools" Color="Color.Primary" Label="@T("Does this chat template preselect tools?")" LabelOn="@T("Yes, this template decides which tools a chat starts with")" LabelOff="@T("No, chats keep the tools from your chat options")" Disabled="@this.IsReadOnly"/>
@if (this.preselectTools)
{
<MudPaper Class="pa-3 mb-8 mt-3 border-dashed border rounded-lg">
<ToolSelectionField Component="Components.CHAT" SelectedToolIds="@this.selectedToolIds" SelectedToolIdsChanged="@this.SetSelectedToolIds" ReadOnly="@this.IsReadOnly" Label="@T("Preselected tools")" Help="@T("The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use.")"/>
</MudPaper>
}
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Data Sources")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options.")
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.preselectDataSources" Color="Color.Primary" Label="@T("Does this chat template preselect data sources?")" LabelOn="@T("Yes, this template decides which data a chat starts with")" LabelOff="@T("No, chats keep the data source options from your chat options")" Disabled="@this.IsReadOnly"/>
@if (this.preselectDataSources)
{
<DataSourceSelection SelectionMode="DataSourceSelectionMode.CONFIGURATION_MODE" AutoSaveAppSettings="@false" ConfiguresChatDefaults="@false" ReadOnly="@this.IsReadOnly" @bind-DataSourceOptions="@this.templateDataSourceOptions" ConfigurationHeaderMessage="@T("A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself.")"/>
}
}
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Example Conversation")
</MudText>

View File

@ -1,6 +1,7 @@
using AIStudio.Chat;
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using Microsoft.AspNetCore.Components;
@ -59,6 +60,18 @@ public partial class ChatTemplateDialog : MSGComponentBase
[Parameter]
public bool AllowProfileUsage { get; set; } = true;
/// <summary>
/// The tools this template preselects, or null when it says nothing about tools.
/// </summary>
[Parameter]
public HashSet<string>? ToolIds { get; set; }
/// <summary>
/// The data source options this template preselects, or null when it says nothing about them.
/// </summary>
[Parameter]
public DataSourceOptions? DataSourceOptions { get; set; }
[Parameter]
public bool CreateFromExistingChatThread { get; set; }
@ -78,6 +91,10 @@ public partial class ChatTemplateDialog : MSGComponentBase
private bool dataIsValid;
private List<ContentBlock> dataExampleConversation = [];
private HashSet<FileAttachment> fileAttachments = [];
private bool preselectTools;
private HashSet<string> selectedToolIds = new(StringComparer.Ordinal);
private bool preselectDataSources;
private DataSourceOptions templateDataSourceOptions = new();
private string[] dataIssues = [];
private string dataEditingPreviousName = string.Empty;
private bool isInlineEditOnGoing;
@ -97,6 +114,20 @@ public partial class ChatTemplateDialog : MSGComponentBase
// Load the used instance names:
this.UsedNames = this.SettingsManager.ConfigurationData.ChatTemplates.Select(x => x.Name.ToLowerInvariant()).ToList();
//
// The two switches below carry the third state of the preselection: switched off, this
// template says nothing, and a chat started with it uses the defaults from the chat
// options. Their working copies live apart from the parameters, so switching a
// preselection off and on again does not throw away what was picked.
//
this.preselectTools = this.ToolIds is not null;
this.selectedToolIds = this.ToolIds is null ? new(StringComparer.Ordinal) : new(this.ToolIds, StringComparer.Ordinal);
this.preselectDataSources = this.DataSourceOptions is not null;
// Saying that this template preselects data sources is already the statement that it wants
// them, so the switch inside the selection starts on instead of at its usual default:
this.templateDataSourceOptions = this.DataSourceOptions?.CreateCopy() ?? new DataSourceOptions { DisableDataSources = false };
// When editing, we need to load the data:
if(this.IsEditing)
{
@ -138,11 +169,15 @@ public partial class ChatTemplateDialog : MSGComponentBase
ExampleConversation = this.dataExampleConversation,
FileAttachments = this.fileAttachments.Select(attachment => attachment.Normalize()).ToList(),
AllowProfileUsage = this.AllowProfileUsage,
ToolIds = this.preselectTools ? new HashSet<string>(this.selectedToolIds, StringComparer.Ordinal) : null,
DataSourceOptions = this.preselectDataSources ? this.templateDataSourceOptions.CreateCopy() : null,
EnterpriseConfigurationPluginId = Guid.Empty,
IsEnterpriseConfiguration = false,
};
private void SetSelectedToolIds(HashSet<string> toolIds) => this.selectedToolIds = toolIds;
private void RemoveMessage(ContentBlock item)
{
if (this.IsReadOnly)

View File

@ -69,6 +69,8 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
{ x => x.ExampleConversation, chatTemplate.ExampleConversation },
{ x => x.FileAttachments, chatTemplate.FileAttachments },
{ x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage },
{ x => x.ToolIds, chatTemplate.ToolIds },
{ x => x.DataSourceOptions, chatTemplate.DataSourceOptions },
};
var dialogReference = await this.DialogService.ShowAsync<ChatTemplateDialog>(T("Edit Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
@ -97,6 +99,8 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
{ x => x.ExampleConversation, chatTemplate.ExampleConversation },
{ x => x.FileAttachments, chatTemplate.FileAttachments },
{ x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage },
{ x => x.ToolIds, chatTemplate.ToolIds },
{ x => x.DataSourceOptions, chatTemplate.DataSourceOptions },
};
await this.DialogService.ShowAsync<ChatTemplateDialog>(T("View Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);