Changes based on code review, changed the profile tooltip, added translation option for chat roles, and added the option to preselect a chat template for chats

This commit is contained in:
Peer Schütt 2025-05-23 15:32:20 +02:00
parent e7b32848e5
commit 10edb1ec54
16 changed files with 71 additions and 56 deletions

View File

@ -89,6 +89,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected MudForm? form; protected MudForm? form;
protected bool inputIsValid; protected bool inputIsValid;
protected Profile currentProfile = Profile.NO_PROFILE; protected Profile currentProfile = Profile.NO_PROFILE;
protected ChatTemplate currentChatTemplate = ChatTemplate.NO_CHATTEMPLATE;
protected ChatThread? chatThread; protected ChatThread? chatThread;
protected IContent? lastUserPrompt; protected IContent? lastUserPrompt;
protected CancellationTokenSource? cancellationTokenSource; protected CancellationTokenSource? cancellationTokenSource;
@ -115,6 +116,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.MightPreselectValues(); this.MightPreselectValues();
this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component); this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component); this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
} }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()

View File

@ -1,3 +1,5 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Chat; namespace AIStudio.Chat;
/// <summary> /// <summary>
@ -19,6 +21,8 @@ public enum ChatRole
/// </summary> /// </summary>
public static class ExtensionsChatRole public static class ExtensionsChatRole
{ {
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatRole).Namespace, nameof(ChatRole));
/// <summary> /// <summary>
/// Returns the name of the role. /// Returns the name of the role.
/// </summary> /// </summary>
@ -26,11 +30,11 @@ public static class ExtensionsChatRole
/// <returns>The name of the role.</returns> /// <returns>The name of the role.</returns>
public static string ToName(this ChatRole role) => role switch public static string ToName(this ChatRole role) => role switch
{ {
ChatRole.SYSTEM => "System", ChatRole.SYSTEM => TB("System"),
ChatRole.USER => "You", ChatRole.USER => TB("You"),
ChatRole.AI => "AI", ChatRole.AI => TB("AI"),
_ => "Unknown", _ => TB("Unknown"),
}; };
/// <summary> /// <summary>
@ -68,10 +72,10 @@ public static class ExtensionsChatRole
/// <returns>The name of the role.</returns> /// <returns>The name of the role.</returns>
public static string ToChatTemplateName(this ChatRole role) => role switch public static string ToChatTemplateName(this ChatRole role) => role switch
{ {
ChatRole.SYSTEM => "System", ChatRole.SYSTEM => TB("System"),
ChatRole.USER => "User", ChatRole.USER => TB("User"),
ChatRole.AI => "Assistant", ChatRole.AI => TB("Assistant"),
_ => "Unknown", _ => TB("Unknown"),
}; };
} }

View File

@ -112,7 +112,7 @@
</MudTooltip> </MudTooltip>
} }
<ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" Disabled="@(!this.currentChatTemplate.AllowProfileUsage)"/> <ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" Disabled="@(!this.currentChatTemplate.AllowProfileUsage)" DisabledText="@T("The profile is disabled according to your chat template settings.")"/>
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{ {

View File

@ -60,7 +60,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private string currentWorkspaceName = string.Empty; private string currentWorkspaceName = string.Empty;
private Guid currentWorkspaceId = Guid.Empty; private Guid currentWorkspaceId = Guid.Empty;
private CancellationTokenSource? cancellationTokenSource; private CancellationTokenSource? cancellationTokenSource;
private bool disableProfile = false; // TODO
// Unfortunately, we need the input field reference to blur the focus away. Without // Unfortunately, we need the input field reference to blur the focus away. Without
// this, we cannot clear the input field. // this, we cannot clear the input field.
@ -80,7 +79,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT); this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
// Get the preselected chat template: // Get the preselected chat template:
// this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT); // TODO this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
// //
// Check for deferred messages of the kind 'SEND_TO_CHAT', // Check for deferred messages of the kind 'SEND_TO_CHAT',
@ -329,18 +328,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate) private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate)
{ {
this.currentChatTemplate = chatTemplate; this.currentChatTemplate = chatTemplate;
this.disableProfile = !chatTemplate.AllowProfileUsage;
if(this.ChatThread is null) if(this.ChatThread is null)
return; return;
this.ChatThread = this.ChatThread with
{
SelectedChatTemplate = this.currentChatTemplate.Id,
Blocks = this.currentChatTemplate.AdditionalMessages.Select(x => x.DeepClone()).ToList(),
};
// await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
await this.StartNewChat(true, false); await this.StartNewChat(true, false);
} }
@ -817,7 +807,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
this.currentChatTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate); this.currentChatTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate);
if(this.currentChatTemplate == default) if(this.currentChatTemplate == default)
this.currentChatTemplate = ChatTemplate.NO_CHATTEMPLATE; this.currentChatTemplate = ChatTemplate.NO_CHATTEMPLATE;
this.disableProfile = !this.currentChatTemplate.AllowProfileUsage;
} }
} }

View File

@ -1,6 +1,6 @@
@inherits MSGComponentBase @inherits MSGComponentBase
<MudTooltip Text="@T("You can switch between your profiles here")" Placement="Placement.Top"> <MudTooltip Text="@this.ToolTipText" Placement="Placement.Top">
<MudMenu TransformOrigin="@Origin.BottomLeft" AnchorOrigin="Origin.TopLeft" StartIcon="@Icons.Material.Filled.Person4" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.CurrentProfile.Name" Variant="Variant.Filled" Color="Color.Default" Class="@this.MarginClass" disabled="@this.Disabled"> <MudMenu TransformOrigin="@Origin.BottomLeft" AnchorOrigin="Origin.TopLeft" StartIcon="@Icons.Material.Filled.Person4" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.CurrentProfile.Name" Variant="Variant.Filled" Color="Color.Default" Class="@this.MarginClass" Disabled="@this.Disabled">
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles()) @foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles())
{ {
<MudMenuItem OnClick="() => this.SelectionChanged(profile)"> <MudMenuItem OnClick="() => this.SelectionChanged(profile)">

View File

@ -1,11 +1,13 @@
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace AIStudio.Components; namespace AIStudio.Components;
public partial class ProfileSelection : MSGComponentBase public partial class ProfileSelection : MSGComponentBase
{ {
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationProviderSelection).Namespace, nameof(ConfigurationProviderSelection));
[Parameter] [Parameter]
public Profile CurrentProfile { get; set; } = Profile.NO_PROFILE; public Profile CurrentProfile { get; set; } = Profile.NO_PROFILE;
@ -21,6 +23,13 @@ public partial class ProfileSelection : MSGComponentBase
[Parameter] [Parameter]
public bool Disabled { get; set; } public bool Disabled { get; set; }
[Parameter]
public string DisabledText { get; set; } = string.Empty;
private readonly string defaultToolTipText = TB("You can switch between your profiles here");
private string ToolTipText => this.Disabled ? this.DisabledText : this.defaultToolTipText;
private string MarginClass => $"{this.MarginLeft} {this.MarginRight}"; private string MarginClass => $"{this.MarginLeft} {this.MarginRight}";
private async Task SelectionChanged(Profile profile) private async Task SelectionChanged(Profile profile)

View File

@ -12,6 +12,7 @@
<ConfigurationOption OptionDescription="@T("Preselect chat options?")" LabelOn="@T("Chat options are preselected")" LabelOff="@T("No chat options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider.")"/> <ConfigurationOption OptionDescription="@T("Preselect chat options?")" LabelOn="@T("Chat options are preselected")" LabelOff="@T("No chat options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider.")"/>
<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)"/> <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="@T("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="@T("Would you like to set one of your profiles as the default for chats?")"/> <ConfigurationSelect OptionDescription="@T("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="@T("Would you like to set one of your profiles as the default for chats?")"/>
<ConfigurationSelect OptionDescription="@T("Preselect one of your chat templates?")" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedChatTemplate)" Data="@ConfigurationSelectDataFactory.GetChatTemplatesData(this.SettingsManager.ConfigurationData.ChatTemplates)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedChatTemplate = selectedValue)" OptionHelp="@T("Would you like to set one of your chat templates as the default for chats?")"/>
</MudPaper> </MudPaper>
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))

View File

@ -30,14 +30,12 @@ public partial class SettingsPanelChatTemplates : SettingsPanelBase
private async Task EditChatTemplate(ChatTemplate chatTemplate) private async Task EditChatTemplate(ChatTemplate chatTemplate)
{ {
// TODO: additionall messages übergeben
var dialogParameters = new DialogParameters<ChatTemplateDialog> var dialogParameters = new DialogParameters<ChatTemplateDialog>
{ {
{ x => x.DataNum, chatTemplate.Num }, { x => x.DataNum, chatTemplate.Num },
{ x => x.DataId, chatTemplate.Id }, { x => x.DataId, chatTemplate.Id },
{ x => x.DataName, chatTemplate.Name }, { x => x.DataName, chatTemplate.Name },
{ x => x.DataSystemPrompt, chatTemplate.SystemPrompt }, { x => x.DataSystemPrompt, chatTemplate.SystemPrompt },
// { x => x.DataActions, chatTemplate.Actions },
{ x => x.IsEditing, true }, { x => x.IsEditing, true },
{x => x.AdditionalMessages, chatTemplate.AdditionalMessages}, {x => x.AdditionalMessages, chatTemplate.AdditionalMessages},
}; };

View File

@ -66,16 +66,16 @@
@T("Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.") @T("Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.")
</MudJustifiedText> </MudJustifiedText>
<MudTable CanCancelEdit="true" Class="mt-3 mb-6" CommitEditTooltip="@T("Commit Changes")" Elevation="10" FixedHeader="true" Items="@AdditionalMessages" Outlined="true" RowEditCancel="@this.ResetItemToOriginalValues" RowEditCommit="@this.ItemHasBeenCommitted" RowEditPreview="@this.BackupItem"> <MudTable CanCancelEdit="true" Class="mt-3 mb-6" CommitEditTooltip="@T("Commit Changes")" Elevation="10" FixedHeader="true" Items="@AdditionalMessages" Outlined="true" RowEditCancel="@this.ResetItemToOriginalValues" RowEditPreview="@this.BackupItem">
<ColGroup> <ColGroup>
<col style="width: 16em;" /> <col style="width: 16em;" />
<col/> <col/>
<col style="width: 16em;" /> <col style="width: 16em;" />
</ColGroup> </ColGroup>
<HeaderContent> <HeaderContent>
<MudTh>Role</MudTh> <MudTh>@T("Role")</MudTh>
<MudTh>Entry</MudTh> <MudTh>@T("Entry")</MudTh>
<MudTh Style="text-align:center">Actions</MudTh> <MudTh Style="text-align:center">@T("Actions")</MudTh>
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
<MudTd DataLabel="@T("Role")">@context.Role.ToChatTemplateName()</MudTd> <MudTd DataLabel="@T("Role")">@context.Role.ToChatTemplateName()</MudTd>
@ -97,11 +97,11 @@
</MudSelect> </MudSelect>
</MudTd> </MudTd>
<MudTd DataLabel="Message"> <MudTd DataLabel="Message">
<MudTextField AutoGrow="true" @bind-Value="context.Content.As<ContentText>()!.Text" Label="Your message" Required /> <MudTextField AutoGrow="true" @bind-Value="context.Content.As<ContentText>()!.Text" Label="@T("Your message")" Required />
</MudTd> </MudTd>
</RowEditingTemplate> </RowEditingTemplate>
<PagerContent> <PagerContent>
<MudTablePager PageSizeOptions="[10,20,50,100]" RowsPerPageString="Messages per page" /> <MudTablePager PageSizeOptions="[10,20,50,100]" RowsPerPageString="@T("Messages per page")" />
</PagerContent> </PagerContent>
</MudTable> </MudTable>
</MudForm> </MudForm>

View File

@ -81,7 +81,6 @@ public partial class ChatTemplateDialog : MSGComponentBase
private void RemoveMessage(ContentBlock item) private void RemoveMessage(ContentBlock item)
{ {
this.AdditionalMessages.Remove(item); this.AdditionalMessages.Remove(item);
// this.Snackbar.Add("Entry removed", Severity.Info);
} }
private void AddNewMessageToEnd() private void AddNewMessageToEnd()
@ -96,7 +95,6 @@ public partial class ChatTemplateDialog : MSGComponentBase
}; };
this.AdditionalMessages.Add(newEntry); this.AdditionalMessages.Add(newEntry);
// this.Snackbar.Add("Initial entry added", Severity.Success);
} }
private void AddNewMessageBelow(ContentBlock currentItem) private void AddNewMessageBelow(ContentBlock currentItem)
@ -118,12 +116,10 @@ public partial class ChatTemplateDialog : MSGComponentBase
if (index >= 0) if (index >= 0)
{ {
this.AdditionalMessages.Insert(index + 1, newEntry); this.AdditionalMessages.Insert(index + 1, newEntry);
// this.Snackbar.Add("New entry added", Severity.Success);
} }
else else
{ {
this.AdditionalMessages.Add(newEntry); this.AdditionalMessages.Add(newEntry);
// this.Snackbar.Add("New entry added", Severity.Success);
} }
} }
@ -136,11 +132,6 @@ public partial class ChatTemplateDialog : MSGComponentBase
}; };
} }
private void ItemHasBeenCommitted(object element)
{
// this.Snackbar.Add("Changes saved", Severity.Success);
}
private void ResetItemToOriginalValues(object element) private void ResetItemToOriginalValues(object element)
{ {
((ContentBlock)element).Role = this.messageEntryBeforeEdit.Role; ((ContentBlock)element).Role = this.messageEntryBeforeEdit.Role;
@ -200,9 +191,6 @@ public partial class ChatTemplateDialog : MSGComponentBase
private string? ValidateSystemPrompt(string text) private string? ValidateSystemPrompt(string text)
{ {
// if (string.IsNullOrWhiteSpace(this.DataSystemPrompt))// && string.IsNullOrWhiteSpace(this.DataActions))
// return T("Please enter the system prompt.");
if(text.Length > 444) if(text.Length > 444)
return T("The text must not exceed 444 characters."); return T("The text must not exceed 444 characters.");

View File

@ -32,16 +32,7 @@ public readonly record struct ChatTemplate(uint Num, string Id, string Name, str
if(this.Num == uint.MaxValue) if(this.Num == uint.MaxValue)
return string.Empty; return string.Empty;
var systemPrompt = return this.SystemPrompt;
$"""
```
{this.SystemPrompt}
```
""";
return $"""
{systemPrompt}
""";
} }
} }

View File

@ -198,6 +198,12 @@ public static class ConfigurationSelectDataFactory
yield return new(profile.Name, profile.Id); yield return new(profile.Name, profile.Id);
} }
public static IEnumerable<ConfigurationSelectData<string>> GetChatTemplatesData(IEnumerable<ChatTemplate> chatTemplates)
{
foreach (var chatTemplate in chatTemplates.GetAllChatTemplates())
yield return new(chatTemplate.Name, chatTemplate.Id);
}
public static IEnumerable<ConfigurationSelectData<ConfidenceSchemes>> GetConfidenceSchemesData() public static IEnumerable<ConfigurationSelectData<ConfidenceSchemes>> GetConfidenceSchemesData()
{ {
foreach (var scheme in Enum.GetValues<ConfidenceSchemes>()) foreach (var scheme in Enum.GetValues<ConfidenceSchemes>())

View File

@ -57,4 +57,10 @@ public sealed class DataApp
/// Should we preselect a profile for the entire app? /// Should we preselect a profile for the entire app?
/// </summary> /// </summary>
public string PreselectedProfile { get; set; } = string.Empty; public string PreselectedProfile { get; set; } = string.Empty;
/// <summary>
/// Should we preselect a chat template for the entire app?
/// </summary>
public string PreselectedChatTemplate { get; set; } = string.Empty;
} }

View File

@ -37,6 +37,11 @@ public sealed class DataChat
/// </summary> /// </summary>
public string PreselectedProfile { get; set; } = string.Empty; public string PreselectedProfile { get; set; } = string.Empty;
/// <summary>
/// Preselect a chat template?
/// </summary>
public string PreselectedChatTemplate { get; set; } = string.Empty;
/// <summary> /// <summary>
/// Should we preselect data sources options for a created chat? /// Should we preselect data sources options for a created chat?
/// </summary> /// </summary>

View File

@ -266,6 +266,16 @@ public sealed class SettingsManager
return preselection != default ? preselection : Profile.NO_PROFILE; return preselection != default ? preselection : Profile.NO_PROFILE;
} }
public ChatTemplate GetPreselectedChatTemplate(Tools.Components component)
{
var preselection = component.PreselectedChatTemplate(this);
if (preselection != default)
return preselection;
preselection = this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedChatTemplate);
return preselection != default ? preselection : ChatTemplate.NO_CHATTEMPLATE;
}
public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider) public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider)
{ {
if(llmProvider is LLMProviders.NONE) if(llmProvider is LLMProviders.NONE)

View File

@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
@ -131,4 +130,11 @@ public static class ComponentsExtensions
_ => default, _ => default,
}; };
public static ChatTemplate PreselectedChatTemplate(this Components component, SettingsManager settingsManager) => component switch
{
Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedChatTemplate) : default,
_ => default,
};
} }