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