2025-07-11 07:57:46 +00:00
using AIStudio.Chat ;
2025-05-24 10:27:00 +00:00
using AIStudio.Settings ;
2025-07-11 07:57:46 +00:00
using Microsoft.AspNetCore.Components ;
2025-05-24 10:27:00 +00:00
2025-05-25 08:33:13 +00:00
namespace AIStudio.Dialogs.Settings ;
2025-05-24 10:27:00 +00:00
2025-05-25 08:33:13 +00:00
public partial class SettingsDialogChatTemplate : SettingsDialogBase
2025-05-24 10:27:00 +00:00
{
2026-07-19 17:56:30 +00:00
private bool isPluginDirectoryDialogOpen ;
2026-06-20 15:06:43 +00:00
[Parameter]
2025-07-11 07:57:46 +00:00
public bool CreateTemplateFromExistingChatThread { get ; set ; }
2026-06-20 15:06:43 +00:00
2025-07-11 07:57:46 +00:00
[Parameter]
public ChatThread ? ExistingChatThread { get ; set ; }
2026-06-20 15:06:43 +00:00
2025-07-11 07:57:46 +00:00
#region Overrides of ComponentBase
2026-06-20 15:06:43 +00:00
2025-07-11 07:57:46 +00:00
/// <inheritdoc />
protected override async Task OnInitializedAsync ( )
{
await base . OnInitializedAsync ( ) ;
2026-06-20 15:06:43 +00:00
if ( this . CreateTemplateFromExistingChatThread )
2025-07-11 07:57:46 +00:00
await this . AddChatTemplate ( ) ;
}
#endregion
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
private async Task AddChatTemplate ( )
{
var dialogParameters = new DialogParameters < ChatTemplateDialog >
{
{ x = > x . IsEditing , false } ,
} ;
2025-07-11 07:57:46 +00:00
if ( this . CreateTemplateFromExistingChatThread )
{
dialogParameters . Add ( x = > x . CreateFromExistingChatThread , this . CreateTemplateFromExistingChatThread ) ;
dialogParameters . Add ( x = > x . ExistingChatThread , this . ExistingChatThread ) ;
}
2025-05-24 10:27:00 +00:00
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 ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
var addedChatTemplate = ( ChatTemplate ) dialogResult . Data ! ;
addedChatTemplate = addedChatTemplate with { Num = this . SettingsManager . ConfigurationData . NextChatTemplateNum + + } ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
this . SettingsManager . ConfigurationData . ChatTemplates . Add ( addedChatTemplate ) ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
await this . SettingsManager . StoreSettings ( ) ;
await this . MessageBus . SendMessage < bool > ( this , Event . CONFIGURATION_CHANGED ) ;
}
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
private async Task EditChatTemplate ( ChatTemplate chatTemplate )
{
2025-08-18 18:40:52 +00:00
if ( chatTemplate = = ChatTemplate . NO_CHAT_TEMPLATE | | chatTemplate . IsEnterpriseConfiguration )
return ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
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 } ,
2025-07-11 07:57:46 +00:00
{ x = > x . PredefinedUserPrompt , chatTemplate . PredefinedUserPrompt } ,
2025-05-24 10:27:00 +00:00
{ x = > x . IsEditing , true } ,
2025-05-24 17:11:28 +00:00
{ x = > x . ExampleConversation , chatTemplate . ExampleConversation } ,
2026-01-01 15:47:15 +00:00
{ x = > x . FileAttachments , chatTemplate . FileAttachments } ,
2025-05-24 17:11:28 +00:00
{ x = > x . AllowProfileUsage , chatTemplate . AllowProfileUsage } ,
2026-09-22 14:39:17 +00:00
{ x = > x . ToolIds , chatTemplate . ToolIds } ,
{ x = > x . DataSourceOptions , chatTemplate . DataSourceOptions } ,
2025-05-24 10:27:00 +00:00
} ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
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 ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
var editedChatTemplate = ( ChatTemplate ) dialogResult . Data ! ;
this . SettingsManager . ConfigurationData . ChatTemplates [ this . SettingsManager . ConfigurationData . ChatTemplates . IndexOf ( chatTemplate ) ] = editedChatTemplate ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
await this . SettingsManager . StoreSettings ( ) ;
await this . MessageBus . SendMessage < bool > ( this , Event . CONFIGURATION_CHANGED ) ;
}
2026-06-20 15:06:43 +00:00
private async Task ViewChatTemplate ( 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 . 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 } ,
2026-09-22 14:39:17 +00:00
{ x = > x . ToolIds , chatTemplate . ToolIds } ,
{ x = > x . DataSourceOptions , chatTemplate . DataSourceOptions } ,
2026-06-20 15:06:43 +00:00
} ;
await this . DialogService . ShowAsync < ChatTemplateDialog > ( T ( "View Chat Template" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
}
2025-05-24 10:27:00 +00:00
private async Task DeleteChatTemplate ( ChatTemplate chatTemplate )
{
2025-08-28 16:51:44 +00:00
var dialogParameters = new DialogParameters < ConfirmDialog >
2025-05-24 10:27:00 +00:00
{
2025-08-28 16:51:44 +00:00
{ x = > x . Message , string . Format ( T ( "Are you sure you want to delete the chat template '{0}'?" ) , chatTemplate . Name ) } ,
2025-05-24 10:27:00 +00:00
} ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
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 ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
this . SettingsManager . ConfigurationData . ChatTemplates . Remove ( chatTemplate ) ;
await this . SettingsManager . StoreSettings ( ) ;
2026-06-20 15:06:43 +00:00
2025-05-24 10:27:00 +00:00
await this . MessageBus . SendMessage < bool > ( this , Event . CONFIGURATION_CHANGED ) ;
}
2026-05-22 13:46:03 +00:00
private async Task ExportChatTemplateWithSharedAttachmentPaths ( ChatTemplate chatTemplate )
{
if ( ! this . SettingsManager . ConfigurationData . App . ShowAdminSettings )
return ;
if ( chatTemplate = = ChatTemplate . NO_CHAT_TEMPLATE | | chatTemplate . IsEnterpriseConfiguration )
return ;
2026-09-22 14:39:17 +00:00
if ( ! await this . ConfirmExportOfLocalDataSources ( chatTemplate ) )
return ;
2026-05-22 13:46:03 +00:00
await this . CopyChatTemplateLuaToClipboard ( chatTemplate ) ;
}
private async Task ExportChatTemplateWithPackagedAttachments ( ChatTemplate chatTemplate )
{
2026-07-19 17:56:30 +00:00
if ( ! this . SettingsManager . ConfigurationData . App . ShowAdminSettings | | this . isPluginDirectoryDialogOpen )
2026-05-22 13:46:03 +00:00
return ;
if ( chatTemplate = = ChatTemplate . NO_CHAT_TEMPLATE | | chatTemplate . IsEnterpriseConfiguration )
return ;
if ( chatTemplate . FileAttachments . Count = = 0 )
{
2026-09-22 14:39:17 +00:00
// That way asks about the local data sources itself, so we must not ask twice:
2026-05-22 13:46:03 +00:00
await this . ExportChatTemplateWithSharedAttachmentPaths ( chatTemplate ) ;
return ;
}
2026-09-22 14:39:17 +00:00
if ( ! await this . ConfirmExportOfLocalDataSources ( chatTemplate ) )
return ;
2026-07-19 17:56:30 +00:00
this . isPluginDirectoryDialogOpen = true ;
try
{
var pluginDirectoryResponse = await this . RustService . SelectDirectory ( T ( "Select configuration plugin folder" ) ) ;
if ( pluginDirectoryResponse . UserCancelled )
return ;
2026-05-22 13:46:03 +00:00
2026-07-19 17:56:30 +00:00
await this . CopyPackagedChatTemplateLuaToClipboard ( chatTemplate , pluginDirectoryResponse . SelectedDirectory ) ;
}
finally
{
this . isPluginDirectoryDialogOpen = false ;
}
2026-05-22 13:46:03 +00:00
}
2026-09-22 14:39:17 +00:00
/// <summary>
/// Asks whether to export a template although it preselects data sources of this machine.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="chatTemplate">The chat template about to be exported.</param>
/// <returns>True when the export may go ahead.</returns>
private async Task < bool > ConfirmExportOfLocalDataSources ( ChatTemplate chatTemplate )
{
var localDataSourceNames = ChatTemplate . GetPreselectedLocalDataSourceNames ( chatTemplate , this . SettingsManager . ConfigurationData . DataSources ) ;
if ( localDataSourceNames . Count = = 0 )
return true ;
var dialogParameters = new DialogParameters < ConfirmDialog >
{
{ 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 < ConfirmDialog > ( T ( "Export Chat Template" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
var dialogResult = await dialogReference . Result ;
return dialogResult is { Canceled : false } ;
}
2026-05-22 13:46:03 +00:00
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 ) )
2026-08-02 19:41:12 +00:00
await this . RustService . CopyText2Clipboard ( luaCode ) ;
2026-05-22 13:46:03 +00:00
}
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 ) )
2026-08-02 19:41:12 +00:00
await this . RustService . CopyText2Clipboard ( luaCode ) ;
2026-05-22 13:46:03 +00:00
}
2025-05-24 10:27:00 +00:00
}