mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-07-04 01:22:57 +00:00
Added chat templates (#474)
This commit is contained in:
parent
35eebc08a5
commit
df062ae6e6
@ -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()
|
||||||
|
@ -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>
|
||||||
@ -60,4 +64,18 @@ public static class ExtensionsChatRole
|
|||||||
|
|
||||||
_ => Icons.Material.Filled.Help,
|
_ => Icons.Material.Filled.Help,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the specific name of the role for the chat template.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="role">The role.</param>
|
||||||
|
/// <returns>The name of the role.</returns>
|
||||||
|
public static string ToChatTemplateName(this ChatRole role) => role switch
|
||||||
|
{
|
||||||
|
ChatRole.SYSTEM => TB("System"),
|
||||||
|
ChatRole.USER => TB("User"),
|
||||||
|
ChatRole.AI => TB("Assistant"),
|
||||||
|
|
||||||
|
_ => TB("Unknown"),
|
||||||
|
};
|
||||||
}
|
}
|
10
app/MindWork AI Studio/Chat/ChatRoles.cs
Normal file
10
app/MindWork AI Studio/Chat/ChatRoles.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace AIStudio.Chat;
|
||||||
|
|
||||||
|
public static class ChatRoles
|
||||||
|
{
|
||||||
|
public static IEnumerable<ChatRole> ChatTemplateRoles()
|
||||||
|
{
|
||||||
|
yield return ChatRole.USER;
|
||||||
|
yield return ChatRole.AI;
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,11 @@ public sealed record ChatThread
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string SelectedProfile { get; set; } = string.Empty;
|
public string SelectedProfile { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the profile selected for the chat thread.
|
||||||
|
/// </summary>
|
||||||
|
public string SelectedChatTemplate { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The data source options for this chat thread.
|
/// The data source options for this chat thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -70,6 +75,8 @@ public sealed record ChatThread
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<ContentBlock> Blocks { get; init; } = [];
|
public List<ContentBlock> Blocks { get; init; } = [];
|
||||||
|
|
||||||
|
private bool allowProfile = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prepares the system prompt for the chat thread.
|
/// Prepares the system prompt for the chat thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -84,16 +91,52 @@ public sealed record ChatThread
|
|||||||
/// <returns>The prepared system prompt.</returns>
|
/// <returns>The prepared system prompt.</returns>
|
||||||
public string PrepareSystemPrompt(SettingsManager settingsManager, ChatThread chatThread, ILogger logger)
|
public string PrepareSystemPrompt(SettingsManager settingsManager, ChatThread chatThread, ILogger logger)
|
||||||
{
|
{
|
||||||
|
//
|
||||||
|
// Use the information from the chat template, if provided. Otherwise, use the default system prompt
|
||||||
|
//
|
||||||
|
string systemPromptTextWithChatTemplate;
|
||||||
|
var logMessage = $"Using no chat template for chat thread '{chatThread.Name}'.";
|
||||||
|
if (string.IsNullOrWhiteSpace(chatThread.SelectedChatTemplate))
|
||||||
|
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!Guid.TryParse(chatThread.SelectedChatTemplate, out var chatTeamplateId))
|
||||||
|
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(chatThread.SelectedChatTemplate == ChatTemplate.NO_CHATTEMPLATE.Id || chatTeamplateId == Guid.Empty)
|
||||||
|
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatThread.SelectedChatTemplate);
|
||||||
|
if(chatTemplate == default)
|
||||||
|
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logMessage = $"Using chat template '{chatTemplate.Name}' for chat thread '{chatThread.Name}'.";
|
||||||
|
this.allowProfile = chatTemplate.AllowProfileUsage;
|
||||||
|
systemPromptTextWithChatTemplate = $"""
|
||||||
|
{chatTemplate.ToSystemPrompt()}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.LogInformation(logMessage);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Add augmented data, if available:
|
||||||
|
//
|
||||||
var isAugmentedDataAvailable = !string.IsNullOrWhiteSpace(chatThread.AugmentedData);
|
var isAugmentedDataAvailable = !string.IsNullOrWhiteSpace(chatThread.AugmentedData);
|
||||||
var systemPromptWithAugmentedData = isAugmentedDataAvailable switch
|
var systemPromptWithAugmentedData = isAugmentedDataAvailable switch
|
||||||
{
|
{
|
||||||
true => $"""
|
true => $"""
|
||||||
{chatThread.SystemPrompt}
|
{systemPromptTextWithChatTemplate}
|
||||||
|
|
||||||
{chatThread.AugmentedData}
|
{chatThread.AugmentedData}
|
||||||
""",
|
""",
|
||||||
|
|
||||||
false => chatThread.SystemPrompt,
|
false => systemPromptTextWithChatTemplate,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(isAugmentedDataAvailable)
|
if(isAugmentedDataAvailable)
|
||||||
@ -101,12 +144,13 @@ public sealed record ChatThread
|
|||||||
else
|
else
|
||||||
logger.LogInformation("No augmented data is available for the chat thread.");
|
logger.LogInformation("No augmented data is available for the chat thread.");
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Prepare the system prompt:
|
// Add information from profile if available and allowed:
|
||||||
//
|
//
|
||||||
string systemPromptText;
|
string systemPromptText;
|
||||||
var logMessage = $"Using no profile for chat thread '{chatThread.Name}'.";
|
logMessage = $"Using no profile for chat thread '{chatThread.Name}'.";
|
||||||
if (string.IsNullOrWhiteSpace(chatThread.SelectedProfile))
|
if ((string.IsNullOrWhiteSpace(chatThread.SelectedProfile)) || (this.allowProfile is false))
|
||||||
systemPromptText = systemPromptWithAugmentedData;
|
systemPromptText = systemPromptWithAugmentedData;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -18,15 +18,27 @@ public class ContentBlock
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The content of the block.
|
/// The content of the block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IContent? Content { get; init; }
|
public IContent? Content { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The role of the content block in the chat thread, e.g., user, AI, etc.
|
/// The role of the content block in the chat thread, e.g., user, AI, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ChatRole Role { get; init; } = ChatRole.NONE;
|
public ChatRole Role { get; set; } = ChatRole.NONE;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should the content block be hidden from the user?
|
/// Should the content block be hidden from the user?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HideFromUser { get; set; }
|
public bool HideFromUser { get; set; }
|
||||||
|
|
||||||
|
public ContentBlock DeepClone()
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Time = this.Time,
|
||||||
|
ContentType = this.ContentType,
|
||||||
|
Content = this.Content?.DeepClone(),
|
||||||
|
Role = this.Role,
|
||||||
|
HideFromUser = this.HideFromUser,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
@ -33,6 +33,18 @@ public sealed class ContentImage : IContent, IImageSource
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IContent DeepClone()
|
||||||
|
{
|
||||||
|
return new ContentImage
|
||||||
|
{
|
||||||
|
Source = this.Source,
|
||||||
|
InitialRemoteWait = this.InitialRemoteWait,
|
||||||
|
IsStreaming = this.IsStreaming,
|
||||||
|
SourceType = this.SourceType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -125,6 +125,17 @@ public sealed class ContentText : IContent
|
|||||||
return chatThread;
|
return chatThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IContent DeepClone()
|
||||||
|
{
|
||||||
|
return new ContentText
|
||||||
|
{
|
||||||
|
Text = this.Text,
|
||||||
|
InitialRemoteWait = this.InitialRemoteWait,
|
||||||
|
IsStreaming = this.IsStreaming,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -43,6 +43,12 @@ public interface IContent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Task<ChatThread> CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastPrompt, ChatThread? chatChatThread, CancellationToken token = default);
|
public Task<ChatThread> CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastPrompt, ChatThread? chatChatThread, CancellationToken token = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a deep copy
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The copy</returns>
|
||||||
|
public IContent DeepClone();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the corresponding ERI content type.
|
/// Returns the corresponding ERI content type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -81,6 +81,11 @@
|
|||||||
</MudTooltip>
|
</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)
|
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||||
{
|
{
|
||||||
<MudTooltip Text="@T("Delete this chat & start a new one.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Delete this chat & start a new one.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
@ -107,7 +112,7 @@
|
|||||||
</MudTooltip>
|
</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))
|
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||||
{
|
{
|
||||||
|
@ -46,6 +46,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
private DataSourceSelection? dataSourceSelectionComponent;
|
private DataSourceSelection? dataSourceSelectionComponent;
|
||||||
private DataSourceOptions earlyDataSourceOptions = new();
|
private DataSourceOptions earlyDataSourceOptions = new();
|
||||||
private Profile currentProfile = Profile.NO_PROFILE;
|
private Profile currentProfile = Profile.NO_PROFILE;
|
||||||
|
private ChatTemplate currentChatTemplate = ChatTemplate.NO_CHATTEMPLATE;
|
||||||
private bool hasUnsavedChanges;
|
private bool hasUnsavedChanges;
|
||||||
private bool mustScrollToBottomAfterRender;
|
private bool mustScrollToBottomAfterRender;
|
||||||
private InnerScrolling scrollingArea = null!;
|
private InnerScrolling scrollingArea = null!;
|
||||||
@ -77,6 +78,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
// Get the preselected profile:
|
// Get the preselected profile:
|
||||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
|
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',
|
// Check for deferred messages of the kind 'SEND_TO_CHAT',
|
||||||
// aka the user sends an assistant result to the chat:
|
// aka the user sends an assistant result to the chat:
|
||||||
@ -319,6 +323,15 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
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()
|
private IReadOnlyList<DataSourceAgentSelected> GetAgentSelectedDataSources()
|
||||||
{
|
{
|
||||||
if (this.ChatThread is null)
|
if (this.ChatThread is null)
|
||||||
@ -413,13 +426,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
SelectedProvider = this.Provider.Id,
|
SelectedProvider = this.Provider.Id,
|
||||||
SelectedProfile = this.currentProfile.Id,
|
SelectedProfile = this.currentProfile.Id,
|
||||||
|
SelectedChatTemplate = this.currentChatTemplate.Id,
|
||||||
SystemPrompt = SystemPrompts.DEFAULT,
|
SystemPrompt = SystemPrompts.DEFAULT,
|
||||||
WorkspaceId = this.currentWorkspaceId,
|
WorkspaceId = this.currentWorkspaceId,
|
||||||
ChatId = Guid.NewGuid(),
|
ChatId = Guid.NewGuid(),
|
||||||
DataSourceOptions = this.earlyDataSourceOptions,
|
DataSourceOptions = this.earlyDataSourceOptions,
|
||||||
Name = this.ExtractThreadName(this.userInput),
|
Name = this.ExtractThreadName(this.userInput),
|
||||||
Seed = this.RNG.Next(),
|
Seed = this.RNG.Next(),
|
||||||
Blocks = [],
|
Blocks = this.currentChatTemplate == default ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||||
@ -430,9 +444,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
|
if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
|
||||||
this.ChatThread.Name = this.ExtractThreadName(this.userInput);
|
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.SelectedProvider = this.Provider.Id;
|
||||||
this.ChatThread.SelectedProfile = this.currentProfile.Id;
|
this.ChatThread.SelectedProfile = this.currentProfile.Id;
|
||||||
|
this.ChatThread.SelectedChatTemplate = this.currentChatTemplate.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
var time = DateTimeOffset.Now;
|
var time = DateTimeOffset.Now;
|
||||||
@ -643,12 +658,13 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
SelectedProvider = this.Provider.Id,
|
SelectedProvider = this.Provider.Id,
|
||||||
SelectedProfile = this.currentProfile.Id,
|
SelectedProfile = this.currentProfile.Id,
|
||||||
|
SelectedChatTemplate = this.currentChatTemplate.Id,
|
||||||
SystemPrompt = SystemPrompts.DEFAULT,
|
SystemPrompt = SystemPrompts.DEFAULT,
|
||||||
WorkspaceId = this.currentWorkspaceId,
|
WorkspaceId = this.currentWorkspaceId,
|
||||||
ChatId = Guid.NewGuid(),
|
ChatId = Guid.NewGuid(),
|
||||||
Name = string.Empty,
|
Name = string.Empty,
|
||||||
Seed = this.RNG.Next(),
|
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 chatProvider = this.ChatThread?.SelectedProvider;
|
||||||
var chatProfile = this.ChatThread?.SelectedProfile;
|
var chatProfile = this.ChatThread?.SelectedProfile;
|
||||||
|
var chatChatTemplate = this.ChatThread?.SelectedChatTemplate;
|
||||||
|
|
||||||
switch (this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)
|
switch (this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)
|
||||||
{
|
{
|
||||||
@ -781,6 +798,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
if(this.currentProfile == default)
|
if(this.currentProfile == default)
|
||||||
this.currentProfile = Profile.NO_PROFILE;
|
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()
|
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
|
@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">
|
<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)">
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -18,6 +20,16 @@ public partial class ProfileSelection : MSGComponentBase
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string MarginRight { get; set; } = string.Empty;
|
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 string MarginClass => $"{this.MarginLeft} {this.MarginRight}";
|
||||||
|
|
||||||
private async Task SelectionChanged(Profile profile)
|
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);
|
||||||
|
}
|
||||||
|
}
|
128
app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
Normal file
128
app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
Normal file
@ -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>
|
224
app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
Normal file
224
app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
Normal file
@ -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();
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<SettingsPanelProfiles AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
<SettingsPanelProfiles AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
||||||
|
<SettingsPanelChatTemplates AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
||||||
<SettingsPanelApp AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
<SettingsPanelApp AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
||||||
<SettingsPanelWorkspaces AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
<SettingsPanelWorkspaces AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
||||||
|
|
||||||
|
38
app/MindWork AI Studio/Settings/ChatTemplate.cs
Normal file
38
app/MindWork AI Studio/Settings/ChatTemplate.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using AIStudio.Chat;
|
||||||
|
using AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
|
namespace AIStudio.Settings;
|
||||||
|
|
||||||
|
public readonly record struct ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, List<ContentBlock> ExampleConversation, bool AllowProfileUsage)
|
||||||
|
{
|
||||||
|
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate));
|
||||||
|
|
||||||
|
public static readonly ChatTemplate NO_CHATTEMPLATE = new()
|
||||||
|
{
|
||||||
|
Name = TB("Use no chat template"),
|
||||||
|
SystemPrompt = string.Empty,
|
||||||
|
Id = Guid.Empty.ToString(),
|
||||||
|
Num = uint.MaxValue,
|
||||||
|
ExampleConversation = [],
|
||||||
|
AllowProfileUsage = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
#region Overrides of ValueType
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string that represents the profile in a human-readable format.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string that represents the profile in a human-readable format.</returns>
|
||||||
|
public override string ToString() => this.Name;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public string ToSystemPrompt()
|
||||||
|
{
|
||||||
|
if(this.Num == uint.MaxValue)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return this.SystemPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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>())
|
||||||
|
@ -36,6 +36,11 @@ public sealed class Data
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Profile> Profiles { get; init; } = [];
|
public List<Profile> Profiles { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of configured chat templates.
|
||||||
|
/// </summary>
|
||||||
|
public List<ChatTemplate> ChatTemplates { get; init; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of enabled plugins.
|
/// List of enabled plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -61,6 +66,11 @@ public sealed class Data
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public uint NextProfileNum { get; set; } = 1;
|
public uint NextProfileNum { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next chat template number to use.
|
||||||
|
/// </summary>
|
||||||
|
public uint NextChatTemplateNum { get; set; } = 1;
|
||||||
|
|
||||||
public DataApp App { get; init; } = new();
|
public DataApp App { get; init; } = new();
|
||||||
|
|
||||||
public DataChat Chat { get; init; } = new();
|
public DataChat Chat { get; init; } = new();
|
||||||
|
@ -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;
|
||||||
}
|
}
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
13
app/MindWork AI Studio/Tools/ChatTemplateExtensions.cs
Normal file
13
app/MindWork AI Studio/Tools/ChatTemplateExtensions.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using AIStudio.Settings;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public static class ChatTemplateExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<ChatTemplate> GetAllChatTemplates(this IEnumerable<ChatTemplate> chatTemplates)
|
||||||
|
{
|
||||||
|
yield return ChatTemplate.NO_CHATTEMPLATE;
|
||||||
|
foreach (var chatTemplate in chatTemplates)
|
||||||
|
yield return chatTemplate;
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
};
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user