diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor
index 5f8ed63f..897e67e7 100644
--- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor
+++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor
@@ -42,6 +42,12 @@ else
}
@code {
+ private protected override RenderFragment? HeaderActions => this.CanReviseCurrentAssistant
+ ? @
+
+
+ : null;
+
private RenderFragment RenderSwitch(AssistantSwitch assistantSwitch) => @
{
+ [Inject]
+ private IDialogService DialogService { get; init; } = null!;
+
[Parameter]
public AssistantForm? RootComponent { get; set; }
@@ -33,6 +40,18 @@ public partial class AssistantDynamic : AssistantBaseCore
///
protected override string AssistantSessionInstanceId => this.assistantPlugin is null ? base.AssistantSessionInstanceId : this.assistantPlugin.Id.ToString();
+ protected override IReadOnlyList FooterButtons =>
+ [
+ new ButtonData
+ {
+ Text = T("Revise"),
+ Icon = Icons.Material.Filled.AutoMode,
+ Color = Color.Default,
+ AsyncAction = this.OpenRevisionDialogAsync,
+ DisabledActionParam = () => !this.CanReviseCurrentAssistant,
+ },
+ ];
+
private string title = string.Empty;
private string description = string.Empty;
private string systemPrompt = string.Empty;
@@ -67,6 +86,9 @@ public partial class AssistantDynamic : AssistantBaseCore
private static readonly AssistantSessionStateKey SECURITY_MESSAGE_STATE_KEY = new(nameof(securityMessage));
private static readonly AssistantSessionStateKey IS_SECURITY_BLOCKED_STATE_KEY = new(nameof(isSecurityBlocked));
+ private bool CanReviseCurrentAssistant => this.assistantPlugin is { IsInternal: false, IsAssistantBuilderGenerated: true } &&
+ !string.IsNullOrWhiteSpace(this.assistantPlugin.PluginPath);
+
///
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
@@ -210,6 +232,93 @@ public partial class AssistantDynamic : AssistantBaseCore
return null;
}
+ private async Task OpenRevisionDialogAsync()
+ {
+ if (this.assistantPlugin is null || !this.CanReviseCurrentAssistant)
+ return;
+
+ var testContext = await this.BuildRevisionTestContextAsync();
+ var parameters = new DialogParameters
+ {
+ { x => x.PluginId, this.assistantPlugin.Id },
+ { x => x.PluginLocalPath, this.assistantPlugin.PluginPath },
+ { x => x.TestContext, testContext },
+ };
+
+ var dialog = await this.DialogService.ShowAsync(this.T("Revise Assistant"), parameters, DialogOptions.BLOCKING_FULLSCREEN);
+ var result = await dialog.Result;
+ if (result is null || result.Canceled)
+ return;
+
+ if (result.Data is not AssistantPluginRevisionDialogResult revisionResult)
+ return;
+
+ this.Logger.LogInformation($"AssistantDynamic of plugin '{revisionResult.PluginName}' ({revisionResult.PluginName}) was successfully revised with audit result {revisionResult.Audit?.Level ?? AssistantAuditLevel.UNKNOWN}.");
+ var updatedPlugin = PluginFactory.RunningPlugins.OfType().FirstOrDefault(x => x.Id == revisionResult.PluginId);
+ if (updatedPlugin is not null)
+ this.ApplyUpdatedAssistantPlugin(updatedPlugin);
+
+ await this.MessageBus.SendSuccess(new(Icons.Material.Filled.AutoFixHigh, string.Format(this.T("The assistant '{0}' has been updated."), revisionResult.PluginName)));
+ await this.MessageBus.SendMessage(this, Event.PLUGINS_RELOADED);
+ await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
+ await this.InvokeAsync(this.StateHasChanged);
+ }
+
+ private async Task BuildRevisionTestContextAsync()
+ {
+ var builder = new StringBuilder();
+
+ if (this.assistantPlugin is not null)
+ {
+ var componentSummary = this.assistantPlugin.CreateAuditComponentSummary();
+ if (!string.IsNullOrWhiteSpace(componentSummary))
+ {
+ builder.AppendLine("Current component overview:");
+ builder.AppendLine(componentSummary);
+ builder.AppendLine();
+ }
+ }
+
+ var promptPreview = await this.CollectUserPromptAsync();
+ if (!string.IsNullOrWhiteSpace(promptPreview))
+ {
+ builder.AppendLine("Current prompt preview from the assistant form:");
+ builder.AppendLine(promptPreview);
+ builder.AppendLine();
+ }
+
+ if (this.ResultingContentBlock?.Content is ContentText text && !string.IsNullOrWhiteSpace(text.Text))
+ {
+ builder.AppendLine("Last assistant response visible in this session:");
+ builder.AppendLine(text.Text);
+ }
+
+ return builder.ToString().Trim();
+ }
+
+ private void ApplyUpdatedAssistantPlugin(PluginAssistants updatedPlugin)
+ {
+ this.assistantPlugin = updatedPlugin;
+ this.RootComponent = updatedPlugin.RootComponent;
+ this.title = updatedPlugin.AssistantTitle;
+ this.description = updatedPlugin.AssistantDescription;
+ this.systemPrompt = updatedPlugin.SystemPrompt;
+ this.submitText = updatedPlugin.SubmitText;
+ this.allowProfiles = updatedPlugin.AllowProfiles;
+ this.showFooterProfileSelection = !updatedPlugin.HasEmbeddedProfileSelection;
+ this.pluginPath = updatedPlugin.PluginPath;
+ var pluginHash = updatedPlugin.ComputeAuditHash();
+ this.audit = this.SettingsManager.ConfigurationData.AssistantPluginAudits.FirstOrDefault(x => x.PluginId == updatedPlugin.Id && x.PluginHash == pluginHash);
+
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, updatedPlugin);
+ this.securityMessage = securityState.CanStartAssistant ? string.Empty : securityState.Description;
+ this.isSecurityBlocked = !securityState.CanStartAssistant;
+
+ this.assistantState.Clear();
+ if (this.RootComponent is not null)
+ this.InitializeComponentState(this.RootComponent.Children);
+ }
+
#endregion
private string ResolveImageSource(AssistantImage image)