Files
AI-Studio/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
T

363 lines
22 KiB
Plaintext
Raw Normal View History

2025-05-24 12:27:00 +02:00
@using AIStudio.Chat
@using AIStudio.Settings
@using AIStudio.Settings.DataModel
2025-05-24 12:27:00 +02:00
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
@* A drop anywhere in this dialog belongs to the dialog, not to the page behind it: *@
@* The name for the drop state is given although nobody uses it: the messages of this dialog
are listed in a table, whose rows would otherwise ask for the same name. *@
<PathDropZone IsArea="@true" Context="isDropTarget">
2025-05-24 12:27:00 +02:00
<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>
2025-05-24 12:27:00 +02:00
<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"
ReadOnly="@this.IsReadOnly"
2025-05-24 12:27:00 +02:00
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
2025-05-24 19:11:28 +02:00
<MudText Typo="Typo.h6" Class="mb-3 mt-3">
@T("System Prompt")
</MudText>
2025-05-24 12:27:00 +02:00
<MudTextField
T="string"
@bind-Text="@this.DataSystemPrompt"
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"
MaxLines="12"
2025-05-24 19:11:28 +02:00
AutoGrow="@true"
2025-05-24 12:27:00 +02:00
Class="mb-3"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Tell the AI your system prompt.")"
ReadOnly="@this.IsReadOnly"
2025-05-24 12:27:00 +02:00
/>
2025-05-24 19:11:28 +02:00
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats.")
</MudJustifiedText>
<MudButton Class="mb-3" Color="Color.Default" OnClick="@this.UseDefaultSystemPrompt" StartIcon="@Icons.Material.Filled.ListAlt" Variant="Variant.Filled" Disabled="@this.IsReadOnly">
2025-05-24 19:11:28 +02:00
@T("Use the default system prompt")
</MudButton>
<ReadFileContent Text="@T("Load system prompt from file")" @bind-FileContent="@this.DataSystemPrompt" EnableDragDrop="true" Disabled="@this.IsReadOnly"/>
2025-07-11 09:57:46 +02:00
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Predefined User Input")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with.")
</MudJustifiedText>
<MudTextField
T="string"
@bind-Text="@this.PredefinedUserPrompt"
AdornmentIcon="@Icons.Material.Filled.ListAlt"
Adornment="Adornment.Start"
Immediate="@true"
Label="@T("What predefined user input do you want to use?")"
Variant="Variant.Outlined"
Lines="4"
MaxLines="12"
AutoGrow="@true"
Class="mb-3"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Tell the AI your predefined user input.")"
ReadOnly="@this.IsReadOnly"
2025-07-11 09:57:46 +02:00
/>
<ReadFileContent Text="@T("Load predefined user input from file")" @bind-FileContent="@this.PredefinedUserPrompt" EnableDragDrop="true" Disabled="@this.IsReadOnly"/>
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("File Attachments")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template.")
</MudJustifiedText>
@if (!this.IsEditing && !this.IsReadOnly)
{
@if (this.HasUnresolvedAttachments)
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Filled" Icon="@Icons.Material.Filled.WarningAmber" Class="mb-3">
@T("Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template.")
</MudAlert>
}
@if (!string.IsNullOrWhiteSpace(this.relinkIssue))
{
<MudAlert Severity="Severity.Error" Class="mb-2">@this.relinkIssue</MudAlert>
}
@for (var index = 0; index < this.attachmentsToRelink.Count; index++)
{
var currentIndex = index;
var attachment = this.attachmentsToRelink[index];
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-2">
<MudTextField T="string" Value="@attachment.ReplacementPath"
ValueChanged="@(value => this.UpdateRelinkPath(currentIndex, value))"
Label="@string.Format(T("Relink attachment: {0}"), attachment.OriginalPath)"
HelperText="@T("Enter the full path of a file on this computer, or remove the attachment.")" Variant="Variant.Outlined" Immediate="@true" />
<MudTooltip Text="@T("Remove this attachment")">
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="@(() => this.RemoveAttachmentToRelink(currentIndex))" />
</MudTooltip>
</MudStack>
}
}
<AttachDocuments
Name="ChatTemplateFileAttachments"
@bind-DocumentPaths="@this.fileAttachments"
UseSmallForm="false"
ValidateMediaFileTypes="false"
Disabled="@this.IsReadOnly"
/>
2025-07-11 09:57:46 +02:00
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Profile Usage")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
2025-05-24 19:11:28 +02:00
@T("Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.")
</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" />
@if (this.AllowProfileUsage)
{
<MudJustifiedText Class="mb-3 mt-3" Typo="Typo.body1">
@T("A chat template may also decide which profile a chat starts with, including starting without one. Without such a decision, those chats start with the profile they would start with anyway. The profile stays changeable in the chat.")
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.preselectProfile" Color="Color.Primary" Label="@T("Does this chat template preselect a profile?")" LabelOn="@T("Yes, this template decides which profile a chat starts with")" LabelOff="@T("No, chats keep their usual profile")" Disabled="@this.IsReadOnly"/>
@if (this.preselectProfile)
{
<MudSelect T="string" @bind-Value="@this.selectedProfileId" Label="@T("Preselected profile")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 mt-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person" Disabled="@this.IsReadOnly">
<MudSelectItem T="string" Value="@Profile.NO_PROFILE.Id">@T("Use no profile")</MudSelectItem>
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles)
{
<MudSelectItem T="string" Value="@profile.Id">@profile.GetSafeName()</MudSelectItem>
}
</MudSelect>
@if (this.IsPreselectedProfileMissing)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The profile this chat template preselects does not exist on this computer. Chats started with it keep their usual profile.")
</MudAlert>
}
}
}
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Provider")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("A chat template may decide which provider, and with it which model, a chat starts with. Without such a decision, those chats start with the provider they would start with anyway. The provider stays changeable in the chat, and it must meet the confidence level required for chats.")
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.preselectProvider" Color="Color.Primary" Label="@T("Does this chat template preselect a provider?")" LabelOn="@T("Yes, this template decides which provider a chat starts with")" LabelOff="@T("No, chats keep their usual provider")" Disabled="@this.IsReadOnly"/>
@if (this.preselectProvider)
{
<MudSelect T="string" @bind-Value="@this.selectedProviderId" Label="@T("Preselected provider")" Validation="@this.ValidatePreselectedProvider" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 mt-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.SmartToy" Disabled="@this.IsReadOnly">
@foreach (var provider in this.SettingsManager.GetConfidentProviders(Components.CHAT))
{
<MudSelectItem T="string" Value="@provider.Id">
<ProviderLabel ProviderSettings="@provider" Text="@provider.InstanceName"/>
</MudSelectItem>
}
</MudSelect>
@if (this.IsPreselectedProviderUnusable)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The provider this chat template preselects does not exist on this computer or does not meet the confidence level required for chats. Chats started with it keep their usual provider.")
</MudAlert>
}
}
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Workspace")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("A chat template may keep its chats in a workspace of its own, which is created when it does not exist yet. This applies whenever a chat would otherwise be a disappearing chat, for example, when you start a new chat outside the workspaces or pick this template in a new chat. A chat you start in a workspace on purpose, for example, from the workspace list, stays in that workspace.")
</MudJustifiedText>
@* While workspaces are off, nothing goes into a workspace, so choosing one here would promise what no chat does: *@
@if (this.AreWorkspacesDisabled)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("Workspaces are switched off in your workspace settings. While they are off, chats with this template belong to no workspace, and they do not move into a workspace when you switch workspaces on again. You can change this choice once workspaces are on.")
</MudAlert>
}
<MudTextSwitch @bind-Value="@this.preselectWorkspace" Color="Color.Primary" Label="@T("Does this chat template keep its chats in a workspace?")" LabelOn="@T("Yes, chats with this template go into a workspace")" LabelOff="@T("No, chats go where they would go anyway")" Disabled="@this.IsWorkspaceChoiceLocked"/>
@if (this.preselectWorkspace)
{
<WorkspaceNameField @bind-WorkspaceName="@this.selectedWorkspaceName" SelectLabel="@T("Existing workspace (Optional)")" Label="@T("Workspace name")" HelperText="@T("Choose an existing workspace or enter a name that should be created when a chat with this template starts.")" Validation="@this.ValidateWorkspaceName" Immediate="@true" ReadOnly="@this.IsWorkspaceChoiceLocked" SelectClass="mb-3 mt-3 rounded-lg" TextFieldClass="mb-3 mt-3"/>
}
2025-05-24 19:11:28 +02:00
<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">
2025-05-24 12:27:00 +02:00
@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>
2025-05-24 19:11:28 +02:00
<MudTable Items="@this.dataExampleConversation" FixedHeader="true" Hover="true" Class="mt-3 mb-3" CanCancelEdit="true" CancelEditTooltip="@T("Cancel")" CommitEditTooltip="@T("Commit Changes")" Outlined="true" RowEditCancel="@this.ResetItem" RowEditPreview="@this.BackupItem" EditTrigger="TableEditTrigger.RowClick" IsEditRowSwitchingBlocked="false" RowEditCommit="@this.CommitInlineEdit">
2025-05-24 12:27:00 +02:00
<ColGroup>
2025-05-24 19:11:28 +02:00
<col style="width: 10em;" />
2025-05-24 12:27:00 +02:00
<col/>
2025-05-24 19:11:28 +02:00
<col style="width: 10em;" />
2025-05-24 12:27:00 +02:00
</ColGroup>
<HeaderContent>
<MudTh>@T("Role")</MudTh>
<MudTh>@T("Entry")</MudTh>
2025-05-24 19:11:28 +02:00
<MudTh>@T("Actions")</MudTh>
2025-05-24 12:27:00 +02:00
</HeaderContent>
<RowTemplate>
2025-05-24 19:11:28 +02:00
<MudTd>
@context.Role.ToChatTemplateName()
</MudTd>
<MudTd>
@switch(context.Content)
{
case ContentText textContent:
<MudTextField AutoGrow="true" Value="@textContent.Text" Placeholder="@T("Enter a message")" ReadOnly="true" Variant="Variant.Text" Validation="@this.ValidateExampleTextMessage"/>
break;
2025-05-24 19:11:28 +02:00
case ContentImage { SourceType: ContentImageSource.URL or ContentImageSource.LOCAL_PATH } imageContent:
<MudImage Src="@imageContent.Source" Alt="@T("Image content")" Fluid="true" />
break;
2025-05-24 19:11:28 +02:00
default:
@T("Unsupported content type")
break;
}
2025-05-24 12:27:00 +02:00
</MudTd>
2025-05-24 19:11:28 +02:00
<MudTd>
@if (!this.isInlineEditOnGoing && !this.IsReadOnly)
2025-05-24 19:11:28 +02:00
{
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudTooltip Text="@T("Add a new message below")">
<MudIconButton Color="Color.Primary" Icon="@Icons.Material.Filled.Add" OnClick="@(() => this.AddMessageBelow(context))"/>
</MudTooltip>
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.RemoveMessage(context))"/>
</MudStack>
}
2025-05-24 12:27:00 +02:00
</MudTd>
</RowTemplate>
<RowEditingTemplate>
2025-05-24 19:11:28 +02:00
<MudTd>
@if (this.IsReadOnly)
{
@context.Role.ToChatTemplateName()
}
else
{
<MudSelect Label="@T("Role")" @bind-Value="@context.Role" Required="true">
@foreach (var role in ChatRoles.ChatTemplateRoles())
{
<MudSelectItem Value="@role">
@role.ToChatTemplateName()
</MudSelectItem>
}
</MudSelect>
}
2025-05-24 12:27:00 +02:00
</MudTd>
2025-05-24 19:11:28 +02:00
<MudTd>
@switch(context.Content)
{
case ContentText textContent:
<MudTextField AutoGrow="true" @bind-Value="@textContent.Text" Label="@T("The message")" Required="true" Immediate="true" Placeholder="@T("Enter a message")" ReadOnly="@this.IsReadOnly"/>
2025-05-24 19:11:28 +02:00
break;
2025-05-24 19:11:28 +02:00
default:
<MudText Typo="Typo.body2">
@T("Only text content is supported in the editing mode yet.")
</MudText>
break;
}
2025-05-24 12:27:00 +02:00
</MudTd>
</RowEditingTemplate>
<PagerContent>
<MudTablePager PageSizeOptions="[10,20,50,100]" RowsPerPageString="@T("Messages per page")" />
</PagerContent>
</MudTable>
</MudForm>
@if (!this.isInlineEditOnGoing && !this.IsReadOnly)
2025-05-24 19:11:28 +02:00
{
<MudButton Class="mb-6" Color="Color.Primary" OnClick="@this.AddMessageToEnd" StartIcon="@Icons.Material.Filled.Add" Variant="Variant.Filled">
@T("Add a message")
</MudButton>
}
2025-05-24 12:27:00 +02:00
<Issues IssuesData="@this.dataIssues"/>
</PathDropZone>
2025-05-24 12:27:00 +02:00
</DialogContent>
<DialogActions>
@if (this.IsReadOnly)
2025-05-24 19:11:28 +02:00
{
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Close")
2025-05-24 19:11:28 +02:00
</MudButton>
}
else
{
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
@if (!this.isInlineEditOnGoing)
{
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.HasUnresolvedAttachments">
@if (this.IsEditing)
{
@T("Update")
}
else
{
@T("Add")
}
</MudButton>
}
}
2025-05-24 12:27:00 +02:00
</DialogActions>
</MudDialog>