Added chat templates (#474)

This commit is contained in:
Peer Schütt authored and GitHub committed 2025-05-24 12:27:00 +02:00
1 parent 35eebc08a5
commit df062ae6e6
27 files changed
+799 -21

No files matched your search

@@ -80,6 +80,11 @@
<MudIconButton Icon="@Icons.Material.Filled.CommentBank" OnClick="() => this.StartNewChat(useSameWorkspace: true)"/>
</MudTooltip>
}
@if (this.SettingsManager.ConfigurationData.ChatTemplates.Count > 0)
{
<ChatTemplateSelection CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/>
}
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
{
@@ -106,9 +111,9 @@
<MudIconButton Icon="@Icons.Material.Filled.Stop" Color="Color.Error" OnClick="() => this.CancelStreaming()"/>
</MudTooltip>
}
<ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged"/>
<ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" Disabled="@(!this.currentChatTemplate.AllowProfileUsage)" DisabledText="@T("Profile usage is disabled according to your chat template settings.")"/>
@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))" DataSourcesAISelected="@this.GetAgentSelectedDataSources()"/>
@@ -46,6 +46,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private DataSourceSelection? dataSourceSelectionComponent;
private DataSourceOptions earlyDataSourceOptions = new();
private Profile currentProfile = Profile.NO_PROFILE;
private ChatTemplate currentChatTemplate = ChatTemplate.NO_CHATTEMPLATE;
private bool hasUnsavedChanges;
private bool mustScrollToBottomAfterRender;
private InnerScrolling scrollingArea = null!;
@@ -59,7 +60,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private string currentWorkspaceName = string.Empty;
private Guid currentWorkspaceId = Guid.Empty;
private CancellationTokenSource? cancellationTokenSource;
// Unfortunately, we need the input field reference to blur the focus away. Without
// this, we cannot clear the input field.
private MudTextField<string> inputField = null!;
@@ -77,6 +78,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
// Get the preselected profile:
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
// Get the preselected chat template:
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
//
// Check for deferred messages of the kind 'SEND_TO_CHAT',
// aka the user sends an assistant result to the chat:
@@ -318,6 +322,15 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
}
private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate)
{
this.currentChatTemplate = chatTemplate;
if(this.ChatThread is null)
return;
await this.StartNewChat(true, false);
}
private IReadOnlyList<DataSourceAgentSelected> GetAgentSelectedDataSources()
{
@@ -413,13 +426,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
{
SelectedProvider = this.Provider.Id,
SelectedProfile = this.currentProfile.Id,
SelectedChatTemplate = this.currentChatTemplate.Id,
SystemPrompt = SystemPrompts.DEFAULT,
WorkspaceId = this.currentWorkspaceId,
ChatId = Guid.NewGuid(),
DataSourceOptions = this.earlyDataSourceOptions,
Name = this.ExtractThreadName(this.userInput),
Seed = this.RNG.Next(),
Blocks = [],
Blocks = this.currentChatTemplate == default ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
};
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
@@ -430,9 +444,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
this.ChatThread.Name = this.ExtractThreadName(this.userInput);
// Update provider and profile:
// Update provider, profile and chat template:
this.ChatThread.SelectedProvider = this.Provider.Id;
this.ChatThread.SelectedProfile = this.currentProfile.Id;
this.ChatThread.SelectedChatTemplate = this.currentChatTemplate.Id;
}
var time = DateTimeOffset.Now;
@@ -643,12 +658,13 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
{
SelectedProvider = this.Provider.Id,
SelectedProfile = this.currentProfile.Id,
SelectedChatTemplate = this.currentChatTemplate.Id,
SystemPrompt = SystemPrompts.DEFAULT,
WorkspaceId = this.currentWorkspaceId,
ChatId = Guid.NewGuid(),
Name = string.Empty,
Seed = this.RNG.Next(),
Blocks = [],
Blocks = this.currentChatTemplate == default ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
};
}
@@ -754,6 +770,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
{
var chatProvider = this.ChatThread?.SelectedProvider;
var chatProfile = this.ChatThread?.SelectedProfile;
var chatChatTemplate = this.ChatThread?.SelectedChatTemplate;
switch (this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)
{
@@ -781,6 +798,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
if(this.currentProfile == default)
this.currentProfile = Profile.NO_PROFILE;
}
// Try to select the chat template:
if (!string.IsNullOrWhiteSpace(chatChatTemplate))
{
this.currentChatTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate);
if(this.currentChatTemplate == default)
this.currentChatTemplate = ChatTemplate.NO_CHATTEMPLATE;
}
}
private async Task ToggleWorkspaceOverlay()
@@ -0,0 +1,20 @@
@using AIStudio.Settings
@inherits MSGComponentBase
<MudTooltip Placement="Placement.Top" Text="@T("Start a new chat with a chat template.")">
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="@Origin.BottomLeft" Class="@this.MarginClass">
<ActivatorContent>
<MudButton IconSize="Size.Large" StartIcon="@Icons.Material.Filled.RateReview">
@(this.CurrentChatTemplate != ChatTemplate.NO_CHATTEMPLATE ? this.CurrentChatTemplate.Name : "")
</MudButton>
</ActivatorContent>
<ChildContent>
@foreach (var chatTemplate in this.SettingsManager.ConfigurationData.ChatTemplates.GetAllChatTemplates())
{
<MudMenuItem OnClick="() => this.SelectionChanged(chatTemplate)">
@chatTemplate.Name
</MudMenuItem>
}
</ChildContent>
</MudMenu>
</MudTooltip>
@@ -0,0 +1,28 @@
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class ChatTemplateSelection : MSGComponentBase
{
[Parameter]
public ChatTemplate CurrentChatTemplate { get; set; } = ChatTemplate.NO_CHATTEMPLATE;
[Parameter]
public EventCallback<ChatTemplate> CurrentChatTemplateChanged { get; set; }
[Parameter]
public string MarginLeft { get; set; } = "ml-1";
[Parameter]
public string MarginRight { get; set; } = string.Empty;
private string MarginClass => $"{this.MarginLeft} {this.MarginRight}";
private async Task SelectionChanged(ChatTemplate chatTemplate)
{
this.CurrentChatTemplate = chatTemplate;
await this.CurrentChatTemplateChanged.InvokeAsync(chatTemplate);
}
}
@@ -1,6 +1,6 @@
@inherits MSGComponentBase
<MudTooltip Text="@T("You can switch between your profiles here")" 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">
<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">
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles())
{
<MudMenuItem OnClick="() => this.SelectionChanged(profile)">
@@ -1,11 +1,13 @@
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class ProfileSelection : MSGComponentBase
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationProviderSelection).Namespace, nameof(ConfigurationProviderSelection));
[Parameter]
public Profile CurrentProfile { get; set; } = Profile.NO_PROFILE;
@@ -18,6 +20,16 @@ public partial class ProfileSelection : MSGComponentBase
[Parameter]
public string MarginRight { get; set; } = string.Empty;
[Parameter]
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 async Task SelectionChanged(Profile profile)
@@ -0,0 +1,53 @@
@inherits SettingsPanelBase
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.RateReview" HeaderText="@T("Configure Chat Templates")">
<MudText Typo="Typo.h4" Class="mb-3">
@T("Your Chat Templates")
</MudText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Customize your AI experience with our Chat Templates! Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, our templates give you full control. Similar to OpenAI's playground, you can define your own system prompts and leverage assistant prompts for providers that support them.")
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Note: This advanced feature is designed for users familiar with prompt engineering concepts. Furthermore, you have to make sure yourself that your chosen provider supports the use of assistant prompts.")
</MudJustifiedText>
<MudTable Items="@this.SettingsManager.ConfigurationData.ChatTemplates" Hover="@true" Class="border-dashed border rounded-lg">
<ColGroup>
<col style="width: 3em;"/>
<col/>
<col style="width: 16em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
<MudTh>@T("Chat Template Name")</MudTh>
<MudTh>@T("Actions")</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Num</MudTd>
<MudTd>@context.Name</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditChatTemplate(context)"/>
</MudTooltip>
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteChatTemplate(context)"/>
</MudTooltip>
</MudStack>
</MudTd>
</RowTemplate>
</MudTable>
@if(this.SettingsManager.ConfigurationData.ChatTemplates.Count == 0)
{
<MudText Typo="Typo.h6" Class="mt-3">
@T("No chat templates configured yet.")
</MudText>
}
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddChatTemplate">
@T("Add Chat Template")
</MudButton>
</ExpansionPanel>
@@ -0,0 +1,73 @@
using AIStudio.Dialogs;
using AIStudio.Settings;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components.Settings;
public partial class SettingsPanelChatTemplates : SettingsPanelBase
{
private async Task AddChatTemplate()
{
var dialogParameters = new DialogParameters<ChatTemplateDialog>
{
{ x => x.IsEditing, false },
};
var dialogReference = await this.DialogService.ShowAsync<ChatTemplateDialog>(T("Add Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var addedChatTemplate = (ChatTemplate)dialogResult.Data!;
addedChatTemplate = addedChatTemplate with { Num = this.SettingsManager.ConfigurationData.NextChatTemplateNum++ };
this.SettingsManager.ConfigurationData.ChatTemplates.Add(addedChatTemplate);
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task EditChatTemplate(ChatTemplate chatTemplate)
{
var dialogParameters = new DialogParameters<ChatTemplateDialog>
{
{ x => x.DataNum, chatTemplate.Num },
{ x => x.DataId, chatTemplate.Id },
{ x => x.DataName, chatTemplate.Name },
{ x => x.DataSystemPrompt, chatTemplate.SystemPrompt },
{ x => x.IsEditing, true },
{x => x.ExampleConversation, chatTemplate.ExampleConversation},
{x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage},
};
var dialogReference = await this.DialogService.ShowAsync<ChatTemplateDialog>(T("Edit Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var editedChatTemplate = (ChatTemplate)dialogResult.Data!;
this.SettingsManager.ConfigurationData.ChatTemplates[this.SettingsManager.ConfigurationData.ChatTemplates.IndexOf(chatTemplate)] = editedChatTemplate;
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task DeleteChatTemplate(ChatTemplate chatTemplate)
{
var dialogParameters = new DialogParameters
{
{ "Message", string.Format(T("Are you sure you want to delete the chat template '{0}'?"), chatTemplate.Name) },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
this.SettingsManager.ConfigurationData.ChatTemplates.Remove(chatTemplate);
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
}