2026-07-15 21:00:25 +02:00
using System.Text ;
using AIStudio.Components ;
using AIStudio.Tools.PluginSystem ;
using AIStudio.Tools.Services ;
using Microsoft.AspNetCore.Components ;
namespace AIStudio.Dialogs ;
public partial class AssistantPluginEditorDialog : MSGComponentBase
{
[Inject]
protected RustService RustService { get ; init ; } = null !;
2026-08-02 21:41:12 +02:00
2026-07-15 21:00:25 +02:00
private const string PLUGIN_FILE_NAME = "plugin.lua" ;
private static readonly ILogger LOGGER = Program . LOGGER_FACTORY . CreateLogger ( nameof ( AssistantPluginEditorDialog ));
private readonly MudBlazor . DialogOptions optionsFullscreen = new ()
{
BackdropClick = false ,
CloseButton = true ,
FullScreen = true ,
FullWidth = true ,
NoHeader = true ,
};
[CascadingParameter]
private IMudDialogInstance MudDialog { get ; set ; } = null !;
[Inject]
2026-08-09 19:01:58 +02:00
private PluginInstallService PluginInstallService { get ; init ; } = null !;
2026-07-15 21:00:25 +02:00
[Parameter]
public Guid PluginId { get ; set ; }
[Parameter]
public string PluginLocalPath { get ; set ; } = string . Empty ;
private IAvailablePlugin ? plugin ;
private CodeEditor ? codeEditor ;
private string pluginFile = string . Empty ;
private string luaCode = string . Empty ;
private string issue = string . Empty ;
private bool isLoading = true ;
private bool isSaving ;
private bool isFullscreen ;
private bool CanSave => this . plugin is not null && ! this . isLoading && ! this . isSaving ;
private string FullscreenIcon => this . isFullscreen ? Icons . Material . Filled . FullscreenExit : Icons . Material . Filled . Fullscreen ;
private string FullscreenLabel => this . isFullscreen ? T ( "Exit fullscreen" ) : T ( "Fullscreen" );
private Func < string > Result2Copy => () => string . IsNullOrEmpty ( this . pluginFile ) ? string . Empty : this . pluginFile ;
protected override async Task OnInitializedAsync ()
{
try
{
this . plugin = PluginFactory . AvailablePlugins
. OfType < IAvailablePlugin >()
. FirstOrDefault ( x => x . Id == this . PluginId && AreSamePath ( x . LocalPath , this . PluginLocalPath ));
if ( this . plugin is null )
{
this . issue = T ( "The assistant plugin could not be resolved." );
return ;
}
if ( this . plugin is { IsInternal : true } || this . plugin . Type is not PluginType . ASSISTANT || string . IsNullOrWhiteSpace ( this . plugin . LocalPath ))
{
this . issue = T ( "This plugin cannot be edited." );
return ;
}
2026-08-28 14:06:24 +02:00
// An assistant an organization rolled out must keep the content its enterprise approval
// was granted for, so only its IT department may change it:
if ( this . plugin . IsManagedByConfigServer )
{
this . issue = T ( "Only locally managed assistant plugins can be edited." );
return ;
}
2026-07-15 21:00:25 +02:00
this . pluginFile = Path . Join ( this . plugin . LocalPath , PLUGIN_FILE_NAME );
if (! File . Exists ( this . pluginFile ))
{
this . issue = T ( "The plugin.lua file could not be found." );
return ;
}
this . luaCode = await File . ReadAllTextAsync ( this . pluginFile , Encoding . UTF8 );
}
catch ( Exception e )
{
this . issue = string . Format ( T ( "The assistant plugin could not be loaded: {0}" ), e . Message );
}
finally
{
this . isLoading = false ;
}
await base . OnInitializedAsync ();
}
private async Task SaveAsync ()
{
if (! this . CanSave || this . plugin is null || this . codeEditor is null )
return ;
this . isSaving = true ;
this . issue = string . Empty ;
await this . InvokeAsync ( this . StateHasChanged );
try
{
var editedLua = await this . codeEditor . GetCodeAsync ();
2026-08-09 19:01:58 +02:00
var result = await this . PluginInstallService . UpdateInstalledAssistantAsync ( this . plugin , editedLua , CancellationToken . None );
2026-07-15 21:00:25 +02:00
if (! result . Success )
{
LOGGER . LogError ( $"Failed to update assistant plugin '{result.PluginName}' ({result.PluginId}) in '{result.PluginDirectory}' with issue '{result.Issue}'." );
this . issue = result . Issue ;
return ;
}
this . MudDialog . Close ( DialogResult . Ok ( new AssistantPluginEditorDialogResult ( result . PluginId , result . PluginName )));
}
finally
{
this . isSaving = false ;
if (! string . IsNullOrWhiteSpace ( this . issue ))
await this . InvokeAsync ( this . StateHasChanged );
}
}
private async Task ToggleFullscreenAsync ()
{
this . isFullscreen = ! this . isFullscreen ;
await this . MudDialog . SetOptionsAsync ( this . isFullscreen ? this . optionsFullscreen : DialogOptions . BLOCKING_FULLSCREEN );
}
private void Cancel () => this . MudDialog . Cancel ();
2026-08-02 21:41:12 +02:00
private async Task CopyToClipboard () => await this . RustService . CopyText2Clipboard ( this . Result2Copy ());
2026-07-15 21:00:25 +02:00
private static bool AreSamePath ( string left , string right )
{
if ( string . IsNullOrWhiteSpace ( left ) || string . IsNullOrWhiteSpace ( right ))
return false ;
var comparison = OperatingSystem . IsWindows ()
? StringComparison . OrdinalIgnoreCase
: StringComparison . Ordinal ;
return string . Equals (
Path . GetFullPath ( left ). TrimEnd ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ),
Path . GetFullPath ( right ). TrimEnd ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ),
comparison );
}
}