mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-05 04:49:40 +00:00
Added chat templates (#474)
This commit is contained in:
1 parent
35eebc08a5
commit
df062ae6e6
27 files changed
+799
-21
No files matched your search
@@ -0,0 +1,128 @@
|
||||
@using AIStudio.Chat
|
||||
@using MudBlazor.Extensions
|
||||
@inherits MSGComponentBase
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
|
||||
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
|
||||
@T("Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
|
||||
@T("The name of the chat template is mandatory. Each chat template must have a unique name.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.DataName"
|
||||
Label="@T("Chat Template Name")"
|
||||
Class="mb-3"
|
||||
Immediate="@true"
|
||||
MaxLength="40"
|
||||
Counter="40"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Badge"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateName"
|
||||
Variant="Variant.Outlined"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
/>
|
||||
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.DataSystemPrompt"
|
||||
Validation="@this.ValidateSystemPrompt"
|
||||
AdornmentIcon="@Icons.Material.Filled.ListAlt"
|
||||
Adornment="Adornment.Start"
|
||||
Immediate="@true"
|
||||
Label="@T("What system prompt do you want to use?")"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="6"
|
||||
AutoGrow="@true"
|
||||
MaxLines="12"
|
||||
Class="mb-3"
|
||||
MaxLength="444"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
HelperText="@T("Tell the AI your system prompt.")"
|
||||
/>
|
||||
|
||||
<MudTooltip Text="@T("Are you unsure which system prompt to use? You can simply start with the default system prompt that AI Studio uses for all chats.")">
|
||||
<MudButton Class="mb-3" Color="Color.Primary" OnClick="@this.UseDefaultSystemPrompt" Size="Size.Small" StartIcon="@Icons.Material.Filled.ListAlt" Variant="Variant.Filled">@T("Use the default system prompt")</MudButton>
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="@T("Using some chat templates in tandem with profiles might cause issues. Therefore, you can preliminarily block the usage of profiles here.")">
|
||||
<MudSwitch @bind-Value="@this.AllowProfileUsage" Class="mb-3" Color="Color.Primary" Label="@T("Allow the use of profiles together with this chat template?")" ThumbIcon="@Icons.Material.Filled.Person4" ThumbIconColor="Color.Default" />
|
||||
</MudTooltip>
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mb-3">
|
||||
@T("Example Conversation")
|
||||
</MudText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@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>
|
||||
|
||||
<MudTable CanCancelEdit="true" Class="mt-3 mb-6" CommitEditTooltip="@T("Commit Changes")" Elevation="10" FixedHeader="true" Items="@ExampleConversation" Outlined="true" RowEditCancel="@this.ResetItemToOriginalValues" RowEditPreview="@this.BackupItem">
|
||||
<ColGroup>
|
||||
<col style="width: 16em;" />
|
||||
<col/>
|
||||
<col style="width: 16em;" />
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>@T("Role")</MudTh>
|
||||
<MudTh>@T("Entry")</MudTh>
|
||||
<MudTh Style="text-align:center">@T("Actions")</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="@T("Role")">@context.Role.ToChatTemplateName()</MudTd>
|
||||
<MudTd DataLabel="@T("Message")">
|
||||
@(context.Content is ContentText textContent ? textContent.Text : context.Content?.ToString())
|
||||
</MudTd>
|
||||
<MudTd style="text-align: center">
|
||||
<MudIconButton Color="Color.Primary" Icon="@Icons.Material.Filled.Add" OnClick="@(() => AddNewMessageBelow(context))" />
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => RemoveMessage(context))" />
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<RowEditingTemplate>
|
||||
<MudTd DataLabel="Role">
|
||||
<MudSelect Label="Role" @bind-Value="context.Role" Required>
|
||||
@foreach (var role in availableRoles)
|
||||
{
|
||||
<MudSelectItem Value="@role">@role.ToChatTemplateName()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Message">
|
||||
<MudTextField AutoGrow="true" @bind-Value="context.Content.As<ContentText>()!.Text" Label="@T("Your message")" Required />
|
||||
</MudTd>
|
||||
</RowEditingTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager PageSizeOptions="[10,20,50,100]" RowsPerPageString="@T("Messages per page")" />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</MudForm>
|
||||
|
||||
<MudButton Class="mb-3" Color="Color.Primary" OnClick="@this.AddNewMessageToEnd" StartIcon="@Icons.Material.Filled.Add" Variant="Variant.Filled">@T("Add additional message")</MudButton>
|
||||
|
||||
<Issues IssuesData="@this.dataIssues"/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
|
||||
@T("Cancel")
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@if(this.IsEditing)
|
||||
{
|
||||
@T("Update")
|
||||
}
|
||||
else
|
||||
{
|
||||
@T("Add")
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,224 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
|
||||
public partial class ChatTemplateDialog : MSGComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The chat template's number in the list.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public uint DataNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The chat template's ID.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataId { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// The chat template name chosen by the user.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// What is the system prompt?
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataSystemPrompt { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Should the dialog be in editing mode?
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsEditing { get; init; }
|
||||
|
||||
[Parameter]
|
||||
public List<ContentBlock> ExampleConversation { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public bool AllowProfileUsage { get; set; } = true;
|
||||
|
||||
[Inject]
|
||||
private ILogger<ProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of used chat template names. We need this to check for uniqueness.
|
||||
/// </summary>
|
||||
private List<string> UsedNames { get; set; } = [];
|
||||
|
||||
private bool dataIsValid;
|
||||
private string[] dataIssues = [];
|
||||
private string dataEditingPreviousName = string.Empty;
|
||||
|
||||
private ContentBlock messageEntryBeforeEdit;
|
||||
private readonly IEnumerable<ChatRole> availableRoles = ChatRoles.ChatTemplateRoles().ToArray();
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
private MudForm form = null!;
|
||||
|
||||
private ChatTemplate CreateChatTemplateSettings() => new()
|
||||
{
|
||||
Num = this.DataNum,
|
||||
Id = this.DataId,
|
||||
|
||||
Name = this.DataName,
|
||||
SystemPrompt = this.DataSystemPrompt,
|
||||
ExampleConversation = this.ExampleConversation,
|
||||
AllowProfileUsage = this.AllowProfileUsage,
|
||||
};
|
||||
|
||||
private void RemoveMessage(ContentBlock item)
|
||||
{
|
||||
this.ExampleConversation.Remove(item);
|
||||
}
|
||||
|
||||
private void AddNewMessageToEnd()
|
||||
{
|
||||
this.ExampleConversation ??= new List<ContentBlock>();
|
||||
|
||||
var newEntry = new ContentBlock
|
||||
{
|
||||
Role = ChatRole.USER, // Default to User
|
||||
Content = new ContentText(),
|
||||
ContentType = ContentType.TEXT,
|
||||
HideFromUser = true,
|
||||
Time = DateTimeOffset.Now,
|
||||
};
|
||||
|
||||
this.ExampleConversation.Add(newEntry);
|
||||
}
|
||||
|
||||
private void AddNewMessageBelow(ContentBlock currentItem)
|
||||
{
|
||||
|
||||
// Create new entry with a valid role
|
||||
var newEntry = new ContentBlock
|
||||
{
|
||||
Role = ChatRole.USER, // Default to User
|
||||
Content = new ContentText(),
|
||||
ContentType = ContentType.TEXT,
|
||||
HideFromUser = true,
|
||||
Time = DateTimeOffset.Now,
|
||||
};
|
||||
|
||||
// Rest of the method remains the same
|
||||
var index = this.ExampleConversation.IndexOf(currentItem);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
this.ExampleConversation.Insert(index + 1, newEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ExampleConversation.Add(newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private void BackupItem(object element)
|
||||
{
|
||||
this.messageEntryBeforeEdit = new ContentBlock
|
||||
{
|
||||
Role = ((ContentBlock)element).Role,
|
||||
Content = ((ContentBlock)element).Content,
|
||||
};
|
||||
}
|
||||
|
||||
private void ResetItemToOriginalValues(object element)
|
||||
{
|
||||
((ContentBlock)element).Role = this.messageEntryBeforeEdit.Role;
|
||||
((ContentBlock)element).Content = this.messageEntryBeforeEdit.Content;
|
||||
}
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Configure the spellchecking for the instance name input:
|
||||
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
|
||||
|
||||
// Load the used instance names:
|
||||
this.UsedNames = this.SettingsManager.ConfigurationData.ChatTemplates.Select(x => x.Name.ToLowerInvariant()).ToList();
|
||||
|
||||
// When editing, we need to load the data:
|
||||
if(this.IsEditing)
|
||||
{
|
||||
this.dataEditingPreviousName = this.DataName.ToLowerInvariant();
|
||||
}
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// Reset the validation when not editing and on the first render.
|
||||
// We don't want to show validation errors when the user opens the dialog.
|
||||
if(!this.IsEditing && firstRender)
|
||||
this.form.ResetValidation();
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task Store()
|
||||
{
|
||||
await this.form.Validate();
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
|
||||
// Use the data model to store the chat template.
|
||||
// We just return this data to the parent component:
|
||||
var addedChatTemplateSettings = this.CreateChatTemplateSettings();
|
||||
|
||||
if(this.IsEditing)
|
||||
this.Logger.LogInformation($"Edited chat template '{addedChatTemplateSettings.Name}'.");
|
||||
else
|
||||
this.Logger.LogInformation($"Created chat template '{addedChatTemplateSettings.Name}'.");
|
||||
|
||||
this.MudDialog.Close(DialogResult.Ok(addedChatTemplateSettings));
|
||||
}
|
||||
|
||||
private string? ValidateSystemPrompt(string text)
|
||||
{
|
||||
if(text.Length > 444)
|
||||
return T("The text must not exceed 444 characters.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return T("Please enter a name for the chat template.");
|
||||
|
||||
if (name.Length > 40)
|
||||
return T("The chat template name must not exceed 40 characters.");
|
||||
|
||||
// The instance name must be unique:
|
||||
var lowerName = name.ToLowerInvariant();
|
||||
if (lowerName != this.dataEditingPreviousName && this.UsedNames.Contains(lowerName))
|
||||
return T("The chat template name must be unique; the chosen name is already in use.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void UseDefaultSystemPrompt()
|
||||
{
|
||||
this.DataSystemPrompt = SystemPrompts.DEFAULT;
|
||||
}
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
}
|
||||
Reference in new issue
Block a user