-
+
}
break;
+ case AssistantComponentType.FILE_ATTACHMENTS:
+ if (component is AssistantFileAttachment fileAttachment)
+ {
+ var fileState = this.assistantState.FileAttachments[fileAttachment.Name];
+
+ @if (!string.IsNullOrWhiteSpace(fileAttachment.Heading))
+ {
+
@fileAttachment.Heading
+ }
+
+
+ }
+ break;
+
case AssistantComponentType.DROPDOWN:
if (component is AssistantDropdown assistantDropdown)
{
diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs
index 7b3b3d69..19cd7183 100644
--- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs
+++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs
@@ -1,17 +1,25 @@
using System.Text;
+using AIStudio.Agents.AssistantAudit;
+using AIStudio.Chat;
+using AIStudio.Dialogs;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings;
+using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using Lua;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
+using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Assistants.Dynamic;
public partial class AssistantDynamic : AssistantBaseCore
{
+ [Inject]
+ private IDialogService DialogService { get; init; } = null!;
+
[Parameter]
public AssistantForm? RootComponent { get; set; }
@@ -27,6 +35,11 @@ public partial class AssistantDynamic : AssistantBaseCore
// Reuse chat-level provider filtering/preselection instead of NONE.
protected override Tools.Components Component => Tools.Components.CHAT;
+ ///
+ /// Gets the plugin ID as the assistant session instance ID.
+ ///
+ protected override string AssistantSessionInstanceId => this.assistantPlugin is null ? base.AssistantSessionInstanceId : this.assistantPlugin.Id.ToString();
+
private string title = string.Empty;
private string description = string.Empty;
private string systemPrompt = string.Empty;
@@ -44,11 +57,72 @@ public partial class AssistantDynamic : AssistantBaseCore
private string securityMessage = string.Empty;
private bool isSecurityBlocked;
private const string ASSISTANT_QUERY_KEY = "assistantId";
+ private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new();
+ private static readonly AssistantSessionStateKey TITLE_STATE_KEY = new(nameof(title));
+ private static readonly AssistantSessionStateKey DESCRIPTION_STATE_KEY = new(nameof(description));
+ private static readonly AssistantSessionStateKey SYSTEM_PROMPT_STATE_KEY = new(nameof(systemPrompt));
+ private static readonly AssistantSessionStateKey ALLOW_PROFILES_STATE_KEY = new(nameof(allowProfiles));
+ private static readonly AssistantSessionStateKey SUBMIT_TEXT_STATE_KEY = new(nameof(submitText));
+ private static readonly AssistantSessionStateKey SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY = new(nameof(showFooterProfileSelection));
+ private static readonly AssistantSessionStateKey ASSISTANT_PLUGIN_STATE_KEY = new(nameof(assistantPlugin));
+ private static readonly AssistantSessionStateKey ASSISTANT_STATE_STATE_KEY = new(nameof(assistantState));
+ private static readonly AssistantSessionStateKey> IMAGE_CACHE_STATE_KEY = new(nameof(imageCache));
+ private static readonly AssistantSessionStateKey> EXECUTING_BUTTON_ACTIONS_STATE_KEY = new(nameof(executingButtonActions));
+ private static readonly AssistantSessionStateKey> EXECUTING_SWITCH_ACTIONS_STATE_KEY = new(nameof(executingSwitchActions));
+ private static readonly AssistantSessionStateKey PLUGIN_PATH_STATE_KEY = new(nameof(pluginPath));
+ private static readonly AssistantSessionStateKey AUDIT_STATE_KEY = new(nameof(audit));
+ 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, IsManagedByConfigServer: false } && !string.IsNullOrWhiteSpace(this.assistantPlugin.PluginPath);
+
+ ///
+ protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
+ {
+ state.Set(TITLE_STATE_KEY, this.title);
+ state.Set(DESCRIPTION_STATE_KEY, this.description);
+ state.Set(SYSTEM_PROMPT_STATE_KEY, this.systemPrompt);
+ state.Set(ALLOW_PROFILES_STATE_KEY, this.allowProfiles);
+ state.Set(SUBMIT_TEXT_STATE_KEY, this.submitText);
+ state.Set(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, this.showFooterProfileSelection);
+ state.Set(ASSISTANT_PLUGIN_STATE_KEY, this.assistantPlugin);
+ state.Set(ASSISTANT_STATE_STATE_KEY, this.assistantState.Clone());
+ state.SetDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
+ state.SetHashSet(EXECUTING_BUTTON_ACTIONS_STATE_KEY, this.executingButtonActions);
+ state.SetHashSet(EXECUTING_SWITCH_ACTIONS_STATE_KEY, this.executingSwitchActions);
+ state.Set(PLUGIN_PATH_STATE_KEY, this.pluginPath);
+ state.Set(AUDIT_STATE_KEY, this.audit);
+ state.Set(SECURITY_MESSAGE_STATE_KEY, this.securityMessage);
+ state.Set(IS_SECURITY_BLOCKED_STATE_KEY, this.isSecurityBlocked);
+ }
+
+ ///
+ protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
+ {
+ state.Restore(TITLE_STATE_KEY, value => this.title = value);
+ state.Restore(DESCRIPTION_STATE_KEY, value => this.description = value);
+ state.Restore(SYSTEM_PROMPT_STATE_KEY, value => this.systemPrompt = value);
+ state.Restore(ALLOW_PROFILES_STATE_KEY, value => this.allowProfiles = value);
+ state.Restore(SUBMIT_TEXT_STATE_KEY, value => this.submitText = value);
+ state.Restore(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, value => this.showFooterProfileSelection = value);
+ state.Restore(ASSISTANT_PLUGIN_STATE_KEY, value => this.assistantPlugin = value);
+ state.Restore(ASSISTANT_STATE_STATE_KEY, value => this.assistantState.CopyFrom(value));
+ state.RestoreDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
+ state.RestoreHashSet(EXECUTING_BUTTON_ACTIONS_STATE_KEY, this.executingButtonActions);
+ state.RestoreHashSet(EXECUTING_SWITCH_ACTIONS_STATE_KEY, this.executingSwitchActions);
+ state.Restore(PLUGIN_PATH_STATE_KEY, value => this.pluginPath = value);
+ state.Restore(AUDIT_STATE_KEY, value => this.audit = value);
+ state.Restore(SECURITY_MESSAGE_STATE_KEY, value => this.securityMessage = value);
+ state.Restore(IS_SECURITY_BLOCKED_STATE_KEY, value => this.isSecurityBlocked = value);
+ }
#region Implementation of AssistantBase
protected override void OnInitialized()
{
+ // Configure the spellchecking for the instance name input:
+ this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
+
var pluginAssistant = this.ResolveAssistantPlugin();
if (pluginAssistant is null)
{
@@ -145,6 +219,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)
@@ -219,6 +380,11 @@ public partial class AssistantDynamic : AssistantBaseCore
private static string GetOptionalStyle(string? style) => string.IsNullOrWhiteSpace(style) ? string.Empty : style;
+ private List CollectFileAttachments() =>
+ this.assistantState.FileAttachments.Values
+ .SelectMany(static state => state.DocumentPaths)
+ .ToList();
+
private bool IsButtonActionRunning(string buttonName) => this.executingButtonActions.Contains(buttonName);
private bool IsSwitchActionRunning(string switchName) => this.executingSwitchActions.Contains(switchName);
@@ -407,7 +573,7 @@ public partial class AssistantDynamic : AssistantBaseCore
}
this.CreateChatThread();
- var time = this.AddUserRequest(await this.CollectUserPromptAsync());
+ var time = this.AddUserRequest(await this.CollectUserPromptAsync(), false, this.CollectFileAttachments());
await this.AddAIResponseAsync(time);
}
diff --git a/app/MindWork AI Studio/Assistants/Dynamic/FileAttachmentState.cs b/app/MindWork AI Studio/Assistants/Dynamic/FileAttachmentState.cs
new file mode 100644
index 00000000..9bda173e
--- /dev/null
+++ b/app/MindWork AI Studio/Assistants/Dynamic/FileAttachmentState.cs
@@ -0,0 +1,8 @@
+using AIStudio.Chat;
+
+namespace AIStudio.Assistants.Dynamic;
+
+public sealed class FileAttachmentState
+{
+ public HashSet DocumentPaths { get; set; } = [];
+}
diff --git a/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs b/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs
index 4c1e1158..ee5d233a 100644
--- a/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs
+++ b/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs
@@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Dialogs.Settings;
+using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.EMail;
@@ -78,6 +79,46 @@ public partial class AssistantEMail : AssistantBaseCore SELECTED_WRITING_STYLE_STATE_KEY = new(nameof(selectedWritingStyle));
+ private static readonly AssistantSessionStateKey INPUT_GREETING_STATE_KEY = new(nameof(inputGreeting));
+ private static readonly AssistantSessionStateKey INPUT_BULLET_POINTS_STATE_KEY = new(nameof(inputBulletPoints));
+ private static readonly AssistantSessionStateKey> BULLET_POINTS_LINES_STATE_KEY = new(nameof(bulletPointsLines));
+ private static readonly AssistantSessionStateKey> SELECTED_FOCI_STATE_KEY = new(nameof(selectedFoci));
+ private static readonly AssistantSessionStateKey INPUT_NAME_STATE_KEY = new(nameof(inputName));
+ private static readonly AssistantSessionStateKey SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
+ private static readonly AssistantSessionStateKey CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
+ private static readonly AssistantSessionStateKey PROVIDE_HISTORY_STATE_KEY = new(nameof(provideHistory));
+ private static readonly AssistantSessionStateKey INPUT_HISTORY_STATE_KEY = new(nameof(inputHistory));
+
+ ///
+ protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
+ {
+ state.Set(SELECTED_WRITING_STYLE_STATE_KEY, this.selectedWritingStyle);
+ state.Set(INPUT_GREETING_STATE_KEY, this.inputGreeting);
+ state.Set(INPUT_BULLET_POINTS_STATE_KEY, this.inputBulletPoints);
+ state.SetList(BULLET_POINTS_LINES_STATE_KEY, this.bulletPointsLines);
+ state.SetHashSet(SELECTED_FOCI_STATE_KEY, this.selectedFoci);
+ state.Set(INPUT_NAME_STATE_KEY, this.inputName);
+ state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
+ state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
+ state.Set(PROVIDE_HISTORY_STATE_KEY, this.provideHistory);
+ state.Set(INPUT_HISTORY_STATE_KEY, this.inputHistory);
+ }
+
+ ///
+ protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
+ {
+ state.Restore(SELECTED_WRITING_STYLE_STATE_KEY, value => this.selectedWritingStyle = value);
+ state.Restore(INPUT_GREETING_STATE_KEY, value => this.inputGreeting = value);
+ state.Restore(INPUT_BULLET_POINTS_STATE_KEY, value => this.inputBulletPoints = value);
+ state.RestoreList(BULLET_POINTS_LINES_STATE_KEY, this.bulletPointsLines);
+ state.Restore(SELECTED_FOCI_STATE_KEY, value => this.selectedFoci = value);
+ state.Restore(INPUT_NAME_STATE_KEY, value => this.inputName = value);
+ state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
+ state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
+ state.Restore(PROVIDE_HISTORY_STATE_KEY, value => this.provideHistory = value);
+ state.Restore(INPUT_HISTORY_STATE_KEY, value => this.inputHistory = value);
+ }
#region Overrides of ComponentBase
diff --git a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor
index 9f19942d..e1973b8a 100644
--- a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor
+++ b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor
@@ -41,7 +41,7 @@
}
else
{
-
+
@foreach (var server in this.SettingsManager.ConfigurationData.ERI.ERIServers)
{
@@ -52,10 +52,10 @@ else
}
-
+
@T("Add ERI server preset")
-
+
@T("Delete this server preset")
@@ -82,18 +82,18 @@ else
}
-
+
@T("Common ERI server settings")
-
-
+
+
-
+
@foreach (var language in Enum.GetValues())
{
@@ -103,12 +103,12 @@ else
@if (this.selectedProgrammingLanguage is ProgrammingLanguages.OTHER)
{
-
+
}
-
+
@foreach (var version in Enum.GetValues())
{
@@ -116,7 +116,7 @@ else
}
-
+
@T("Download specification")
@@ -126,7 +126,7 @@ else
-
+
@foreach (var dataSource in Enum.GetValues())
{
@@ -136,21 +136,21 @@ else
@if (this.selectedDataSource is DataSources.CUSTOM)
{
-
+
}
@if(this.selectedDataSource > DataSources.FILE_SYSTEM)
{
-
+
}
@if (this.NeedHostnamePort())
{
-
-
+
+
@if (this.dataSourcePort < 1024)
{
@@ -168,7 +168,7 @@ else
}
-
+
@if (this.selectedAuthenticationMethods.Contains(Auth.KERBEROS))
{
-
+
@foreach (var os in Enum.GetValues())
{
@@ -204,7 +204,7 @@ else
@T("Data protection settings")
-
+
@foreach (var option in Enum.GetValues())
{
@@ -227,7 +227,7 @@ else
@if (!this.IsNoneERIServerSelected)
{
-
+
@@ -243,10 +243,10 @@ else
@context.EmbeddingType
-
+
@T("Edit")
-
+
@T("Delete")
@@ -262,7 +262,7 @@ else
}
}
-
+
@T("Add Embedding Method")
@@ -276,7 +276,7 @@ else
@if (!this.IsNoneERIServerSelected)
{
-
+
@@ -289,10 +289,10 @@ else
@context.Name
-
+
@T("Edit")
-
+
@T("Delete")
@@ -308,7 +308,7 @@ else
}
}
-
+
@T("Add Retrieval Process")
@@ -316,7 +316,7 @@ else
@T("You can integrate additional libraries. Perhaps you want to evaluate the prompts in advance using a machine learning method or analyze them with a text mining approach? Or maybe you want to preprocess images in the prompts? For such advanced scenarios, you can specify which libraries you want to use here. It's best to describe which library you want to integrate for which purpose. This way, the LLM that writes the ERI server for you can try to use these libraries effectively. This should result in less rework being necessary. If you don't know the necessary libraries, you can instead attempt to describe the intended use. The LLM can then attempt to choose suitable libraries. However, hallucinations can occur, and fictional libraries might be selected.")
-
+
@T("Provider selection for generation")
@@ -330,7 +330,7 @@ else
@T("Important:") @T("The LLM may need to generate many files. This reaches the request limit of most providers. Typically, only a certain number of requests can be made per minute, and only a maximum number of tokens can be generated per minute. AI Studio automatically considers this.") @T("However, generating all the files takes a certain amount of time.") @T("Local or self-hosted models may work without these limitations and can generate responses faster. AI Studio dynamically adapts its behavior and always tries to achieve the fastest possible data processing.")
-
+
@T("Write code to file system")
@@ -344,5 +344,5 @@ else
@T("When you rebuild / re-generate the ERI server code, AI Studio proceeds as follows: All files generated last time will be deleted. All other files you have created remain. Then, the AI generates the new files.") @T("But beware:") @T("It may happen that the AI generates a file this time that you manually created last time. In this case, your manually created file will then be overwritten. Therefore, you should always create a Git repository and commit or revert all changes before using this assistant. With a diff visualization, you can immediately see where the AI has made changes. It is best to use an IDE suitable for your selected language for this purpose.")
-
-
+
+
diff --git a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs
index a4c204c9..c6725c33 100644
--- a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs
+++ b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs
@@ -5,6 +5,7 @@ using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings.DataModel;
+using AIStudio.Tools.AssistantSessions;
using Microsoft.AspNetCore.Components;
@@ -291,7 +292,17 @@ public partial class AssistantERI : AssistantBaseCore
}
}
- protected override IReadOnlyList FooterButtons => [];
+ protected override IReadOnlyList FooterButtons =>
+ [
+ new ButtonData
+ {
+ Text = T("Open in chat"),
+ Icon = Icons.Material.Filled.Chat,
+ Color = Color.Default,
+ AsyncAction = this.OpenInChat,
+ DisabledActionParam = () => !this.CanOpenInChat,
+ },
+ ];
protected override bool ShowEntireChatThread => true;
@@ -307,6 +318,22 @@ public partial class AssistantERI : AssistantBaseCore
{
SystemPrompt = this.SystemPrompt,
};
+
+ ///
+ /// Indicates whether the generated ERI conversation can be opened in the chat view.
+ ///
+ private bool CanOpenInChat => !this.IsProcessing && this.ChatThread is { Blocks.Count: > 0 };
+
+ ///
+ /// Opens the generated ERI conversation in the chat view when a finished conversation is available.
+ ///
+ private async Task OpenInChat()
+ {
+ if (!this.CanOpenInChat)
+ return;
+
+ await this.SendToAssistant(Tools.Components.CHAT, default);
+ }
protected override void ResetForm()
{
@@ -449,17 +476,110 @@ public partial class AssistantERI : AssistantBaseCore
private bool writeToFilesystem;
private string baseDirectory = string.Empty;
private List previouslyGeneratedFiles = new();
+ private static readonly AssistantSessionStateKey SELECTED_ERI_SERVER_STATE_KEY = new(nameof(selectedERIServer));
+ private static readonly AssistantSessionStateKey AUTO_SAVE_STATE_KEY = new(nameof(autoSave));
+ private static readonly AssistantSessionStateKey SERVER_NAME_STATE_KEY = new(nameof(serverName));
+ private static readonly AssistantSessionStateKey SERVER_DESCRIPTION_STATE_KEY = new(nameof(serverDescription));
+ private static readonly AssistantSessionStateKey SELECTED_ERI_VERSION_STATE_KEY = new(nameof(selectedERIVersion));
+ private static readonly AssistantSessionStateKey ERI_SPECIFICATION_STATE_KEY = new(nameof(eriSpecification));
+ private static readonly AssistantSessionStateKey SELECTED_PROGRAMMING_LANGUAGE_STATE_KEY = new(nameof(selectedProgrammingLanguage));
+ private static readonly AssistantSessionStateKey OTHER_PROGRAMMING_LANGUAGE_STATE_KEY = new(nameof(otherProgrammingLanguage));
+ private static readonly AssistantSessionStateKey SELECTED_DATA_SOURCE_STATE_KEY = new(nameof(selectedDataSource));
+ private static readonly AssistantSessionStateKey OTHER_DATA_SOURCE_STATE_KEY = new(nameof(otherDataSource));
+ private static readonly AssistantSessionStateKey DATA_SOURCE_PRODUCT_NAME_STATE_KEY = new(nameof(dataSourceProductName));
+ private static readonly AssistantSessionStateKey DATA_SOURCE_HOSTNAME_STATE_KEY = new(nameof(dataSourceHostname));
+ private static readonly AssistantSessionStateKey DATA_SOURCE_PORT_STATE_KEY = new(nameof(dataSourcePort));
+ private static readonly AssistantSessionStateKey