using AIStudio.Chat;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs.Settings;
public partial class SettingsDialogChatTemplate : SettingsDialogBase
{
private bool isPluginDirectoryDialogOpen;
[Parameter]
public bool CreateTemplateFromExistingChatThread { get; set; }
[Parameter]
public ChatThread? ExistingChatThread { get; set; }
#region Overrides of ComponentBase
///
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (this.CreateTemplateFromExistingChatThread)
await this.AddChatTemplate();
}
#endregion
private async Task AddChatTemplate()
{
var dialogParameters = new DialogParameters
{
{ x => x.IsEditing, false },
};
if (this.CreateTemplateFromExistingChatThread)
{
dialogParameters.Add(x => x.CreateFromExistingChatThread, this.CreateTemplateFromExistingChatThread);
dialogParameters.Add(x => x.ExistingChatThread, this.ExistingChatThread);
}
var dialogReference = await this.DialogService.ShowAsync(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(this, Event.CONFIGURATION_CHANGED);
}
private async Task EditChatTemplate(ChatTemplate chatTemplate)
{
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
return;
var dialogParameters = new DialogParameters
{
{ x => x.DataNum, chatTemplate.Num },
{ x => x.DataId, chatTemplate.Id },
{ x => x.DataName, chatTemplate.Name },
{ x => x.DataSystemPrompt, chatTemplate.SystemPrompt },
{ x => x.PredefinedUserPrompt, chatTemplate.PredefinedUserPrompt },
{ x => x.IsEditing, true },
{ x => x.ExampleConversation, chatTemplate.ExampleConversation },
{ x => x.FileAttachments, chatTemplate.FileAttachments },
{ x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage },
{ x => x.ToolIds, chatTemplate.ToolIds },
{ x => x.DataSourceOptions, chatTemplate.DataSourceOptions },
};
var dialogReference = await this.DialogService.ShowAsync(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(this, Event.CONFIGURATION_CHANGED);
}
private async Task ViewChatTemplate(ChatTemplate chatTemplate)
{
var dialogParameters = new DialogParameters
{
{ x => x.DataNum, chatTemplate.Num },
{ x => x.DataId, chatTemplate.Id },
{ x => x.DataName, chatTemplate.Name },
{ x => x.DataSystemPrompt, chatTemplate.SystemPrompt },
{ x => x.PredefinedUserPrompt, chatTemplate.PredefinedUserPrompt },
{ x => x.IsEditing, true },
{ x => x.IsReadOnly, true },
{ x => x.ExampleConversation, chatTemplate.ExampleConversation },
{ x => x.FileAttachments, chatTemplate.FileAttachments },
{ x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage },
{ x => x.ToolIds, chatTemplate.ToolIds },
{ x => x.DataSourceOptions, chatTemplate.DataSourceOptions },
};
await this.DialogService.ShowAsync(T("View Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
}
private async Task DeleteChatTemplate(ChatTemplate chatTemplate)
{
var dialogParameters = new DialogParameters
{
{ x => x.Message, string.Format(T("Are you sure you want to delete the chat template '{0}'?"), chatTemplate.Name) },
};
var dialogReference = await this.DialogService.ShowAsync(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(this, Event.CONFIGURATION_CHANGED);
}
private async Task ExportChatTemplateWithSharedAttachmentPaths(ChatTemplate chatTemplate)
{
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
return;
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
return;
if (!await this.ConfirmExportOfLocalDataSources(chatTemplate))
return;
await this.CopyChatTemplateLuaToClipboard(chatTemplate);
}
private async Task ExportChatTemplateWithPackagedAttachments(ChatTemplate chatTemplate)
{
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings || this.isPluginDirectoryDialogOpen)
return;
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
return;
if (chatTemplate.FileAttachments.Count == 0)
{
// That way asks about the local data sources itself, so we must not ask twice:
await this.ExportChatTemplateWithSharedAttachmentPaths(chatTemplate);
return;
}
if (!await this.ConfirmExportOfLocalDataSources(chatTemplate))
return;
this.isPluginDirectoryDialogOpen = true;
try
{
var pluginDirectoryResponse = await this.RustService.SelectDirectory(T("Select configuration plugin folder"));
if (pluginDirectoryResponse.UserCancelled)
return;
await this.CopyPackagedChatTemplateLuaToClipboard(chatTemplate, pluginDirectoryResponse.SelectedDirectory);
}
finally
{
this.isPluginDirectoryDialogOpen = false;
}
}
///
/// Asks whether to export a template although it preselects data sources of this machine.
///
///
/// The export writes the preselected data source IDs unchanged, which is what makes a template
/// usable across an organization — but a local file or folder exists here and nowhere else, so
/// its ID points at nothing on the machine reading the plugin. Nothing breaks, the chat simply
/// starts without that source, and that is precisely why it has to be said beforehand: nobody
/// would notice it afterwards. Exporting anyway is a fair choice, because the rest of the
/// template is worth rolling out.
///
/// The chat template about to be exported.
/// True when the export may go ahead.
private async Task ConfirmExportOfLocalDataSources(ChatTemplate chatTemplate)
{
var localDataSourceNames = ChatTemplate.GetPreselectedLocalDataSourceNames(chatTemplate, this.SettingsManager.ConfigurationData.DataSources);
if (localDataSourceNames.Count == 0)
return true;
var dialogParameters = new DialogParameters
{
{ x => x.Message, string.Format(T("This chat template preselects data sources which exist on this machine only: {0}. They cannot be rolled out, so a chat started with this template elsewhere begins without them. Do you want to export the template anyway?"), string.Join(", ", localDataSourceNames)) },
};
var dialogReference = await this.DialogService.ShowAsync(T("Export Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
return dialogResult is { Canceled: false };
}
private async Task CopyChatTemplateLuaToClipboard(ChatTemplate chatTemplate)
{
if (!chatTemplate.TryExportAsConfigurationSection(out var luaCode, out var issue))
{
await this.DialogService.ShowMessageBox(
T("Export Chat Template"),
issue,
T("Close"));
return;
}
if (!string.IsNullOrWhiteSpace(luaCode))
await this.RustService.CopyText2Clipboard(luaCode);
}
private async Task CopyPackagedChatTemplateLuaToClipboard(ChatTemplate chatTemplate, string pluginDirectory)
{
if (!chatTemplate.TryExportAsConfigurationSectionWithPackagedAttachments(pluginDirectory, out var luaCode, out var issue))
{
await this.DialogService.ShowMessageBox(
T("Export Chat Template"),
issue,
T("Close"));
return;
}
if (!string.IsNullOrWhiteSpace(luaCode))
await this.RustService.CopyText2Clipboard(luaCode);
}
}