Refactor ERI server input controls to improve state management and disable logic

This commit is contained in:
Thorsten Sommer 2026-07-03 14:02:04 +02:00
parent beabc8ef02
commit 490240c6cd
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
2 changed files with 101 additions and 39 deletions

View File

@ -41,7 +41,7 @@
}
else
{
<MudList Disabled="@this.AreServerPresetsBlocked" T="DataERIServer" Class="mb-1" SelectedValue="@this.selectedERIServer" SelectedValueChanged="@this.SelectedERIServerChanged">
<MudList Disabled="@this.AreServerPresetControlsDisabled" T="DataERIServer" Class="mb-1" SelectedValue="@this.selectedERIServer" SelectedValueChanged="@this.SelectedERIServerChanged">
@foreach (var server in this.SettingsManager.ConfigurationData.ERI.ERIServers)
{
<MudListItem T="DataERIServer" Icon="@Icons.Material.Filled.Settings" Value="@server">
@ -52,10 +52,10 @@ else
}
<MudStack Row="@true" Class="mt-1">
<MudButton Disabled="@this.AreServerPresetsBlocked" OnClick="@this.AddERIServer" Variant="Variant.Filled" Color="Color.Primary">
<MudButton Disabled="@this.AreServerPresetControlsDisabled" OnClick="@this.AddERIServer" Variant="Variant.Filled" Color="Color.Primary">
@T("Add ERI server preset")
</MudButton>
<MudButton OnClick="@this.RemoveERIServer" Disabled="@(this.AreServerPresetsBlocked || this.IsNoneERIServerSelected)" Variant="Variant.Filled" Color="Color.Error">
<MudButton OnClick="@this.RemoveERIServer" Disabled="@(this.AreServerPresetControlsDisabled || this.IsNoneERIServerSelected)" Variant="Variant.Filled" Color="Color.Error">
@T("Delete this server preset")
</MudButton>
</MudStack>
@ -82,18 +82,18 @@ else
</MudJustifiedText>
}
<MudTextSwitch Label="@T("Should we automatically save any input made?")" Disabled="@this.AreServerPresetsBlocked" @bind-Value="@this.autoSave" LabelOn="@T("Yes, please save my inputs")" LabelOff="@T("No, I will enter everything again or configure it manually in the settings")" />
<MudTextSwitch Label="@T("Should we automatically save any input made?")" Disabled="@this.AreServerPresetControlsDisabled" @bind-Value="@this.autoSave" LabelOn="@T("Yes, please save my inputs")" LabelOff="@T("No, I will enter everything again or configure it manually in the settings")" />
<hr style="width: 100%; border-width: 0.25ch;" class="mt-6"/>
<MudText Typo="Typo.h4" Class="mt-6 mb-1">
@T("Common ERI server settings")
</MudText>
<MudTextField T="string" Disabled="@this.IsNoneERIServerSelected" @bind-Text="@this.serverName" Validation="@this.ValidateServerName" Immediate="@true" Label="@T("ERI server name")" HelperText="@T("Please give your ERI server a name that provides information about the data source and/or its intended purpose. The name will be displayed to users in AI Studio.")" Counter="60" MaxLength="60" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" OnKeyUp="() => this.ServerNameWasChanged()"/>
<MudTextField T="string" Disabled="@this.IsNoneERIServerSelected" @bind-Text="@this.serverDescription" Validation="@this.ValidateServerDescription" Immediate="@true" Label="@T("ERI server description")" HelperText="@T("Please provide a brief description of your ERI server. Describe or explain what your ERI server does and what data it uses for this purpose. This description will be shown to users in AI Studio.")" Counter="512" MaxLength="512" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudTextField T="string" Disabled="@this.IsERIInputDisabled" @bind-Text="@this.serverName" Validation="@this.ValidateServerName" Immediate="@true" Label="@T("ERI server name")" HelperText="@T("Please give your ERI server a name that provides information about the data source and/or its intended purpose. The name will be displayed to users in AI Studio.")" Counter="60" MaxLength="60" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" OnKeyUp="() => this.ServerNameWasChanged()"/>
<MudTextField T="string" Disabled="@this.IsERIInputDisabled" @bind-Text="@this.serverDescription" Validation="@this.ValidateServerDescription" Immediate="@true" Label="@T("ERI server description")" HelperText="@T("Please provide a brief description of your ERI server. Describe or explain what your ERI server does and what data it uses for this purpose. This description will be shown to users in AI Studio.")" Counter="512" MaxLength="512" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudStack Row="@true" Class="mb-3">
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="ProgrammingLanguages" @bind-Value="@this.selectedProgrammingLanguage" AdornmentIcon="@Icons.Material.Filled.Code" Adornment="Adornment.Start" Label="@T("Programming language")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateProgrammingLanguage">
<MudSelect Disabled="@this.IsERIInputDisabled" T="ProgrammingLanguages" @bind-Value="@this.selectedProgrammingLanguage" AdornmentIcon="@Icons.Material.Filled.Code" Adornment="Adornment.Start" Label="@T("Programming language")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateProgrammingLanguage">
@foreach (var language in Enum.GetValues<ProgrammingLanguages>())
{
<MudSelectItem Value="@language">
@ -103,12 +103,12 @@ else
</MudSelect>
@if (this.selectedProgrammingLanguage is ProgrammingLanguages.OTHER)
{
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.otherProgrammingLanguage" Validation="@this.ValidateOtherLanguage" Label="@T("Other language")" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudTextField Disabled="@this.IsERIInputDisabled" T="string" @bind-Text="@this.otherProgrammingLanguage" Validation="@this.ValidateOtherLanguage" Label="@T("Other language")" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
</MudStack>
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="ERIVersion" @bind-Value="@this.selectedERIVersion" Label="@T("ERI specification version")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateERIVersion">
<MudSelect Disabled="@this.IsERIInputDisabled" T="ERIVersion" @bind-Value="@this.selectedERIVersion" Label="@T("ERI specification version")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateERIVersion">
@foreach (var version in Enum.GetValues<ERIVersion>())
{
<MudSelectItem Value="@version">
@ -116,7 +116,7 @@ else
</MudSelectItem>
}
</MudSelect>
<MudButton Variant="Variant.Outlined" Size="Size.Small" Disabled="@(!this.selectedERIVersion.WasSpecificationSelected() || this.IsNoneERIServerSelected)" Href="@this.selectedERIVersion.SpecificationURL()" Target="_blank">
<MudButton Variant="Variant.Outlined" Size="Size.Small" Disabled="@this.IsSpecificationDownloadDisabled" Href="@this.selectedERIVersion.SpecificationURL()" Target="_blank">
<MudIcon Icon="@Icons.Material.Filled.Link" Class="mr-2"/> @T("Download specification")
</MudButton>
</MudStack>
@ -126,7 +126,7 @@ else
</MudText>
<MudStack Row="@false" Spacing="1" Class="mb-3">
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="DataSources" @bind-Value="@this.selectedDataSource" AdornmentIcon="@Icons.Material.Filled.Dataset" Adornment="Adornment.Start" Label="@T("Data source")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateDataSource" SelectedValuesChanged="@this.DataSourceWasChanged">
<MudSelect Disabled="@this.IsERIInputDisabled" T="DataSources" @bind-Value="@this.selectedDataSource" AdornmentIcon="@Icons.Material.Filled.Dataset" Adornment="Adornment.Start" Label="@T("Data source")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateDataSource" SelectedValuesChanged="@this.DataSourceWasChanged">
@foreach (var dataSource in Enum.GetValues<DataSources>())
{
<MudSelectItem Value="@dataSource">
@ -136,21 +136,21 @@ else
</MudSelect>
@if (this.selectedDataSource is DataSources.CUSTOM)
{
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.otherDataSource" Validation="@this.ValidateOtherDataSource" Label="@T("Describe your data source")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudTextField Disabled="@this.IsERIInputDisabled" T="string" @bind-Text="@this.otherDataSource" Validation="@this.ValidateOtherDataSource" Label="@T("Describe your data source")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
</MudStack>
@if(this.selectedDataSource > DataSources.FILE_SYSTEM)
{
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.dataSourceProductName" Label="@T("Data source: product name")" Validation="@this.ValidateDataSourceProductName" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudTextField Disabled="@this.IsERIInputDisabled" T="string" @bind-Text="@this.dataSourceProductName" Label="@T("Data source: product name")" Validation="@this.ValidateDataSourceProductName" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
}
@if (this.NeedHostnamePort())
{
<div class="mb-3">
<MudStack Row="@true">
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.dataSourceHostname" Label="@T("Data source: hostname")" Validation="@this.ValidateHostname" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudNumericField Disabled="@this.IsNoneERIServerSelected" Label="@T("Data source: port")" Immediate="@true" Min="1" Max="65535" Validation="@this.ValidatePort" @bind-Value="@this.dataSourcePort" Variant="Variant.Outlined" Margin="Margin.Dense" OnKeyUp="() => this.DataSourcePortWasTyped()"/>
<MudTextField Disabled="@this.IsERIInputDisabled" T="string" @bind-Text="@this.dataSourceHostname" Label="@T("Data source: hostname")" Validation="@this.ValidateHostname" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudNumericField Disabled="@this.IsERIInputDisabled" Label="@T("Data source: port")" Immediate="@true" Min="1" Max="65535" Validation="@this.ValidatePort" @bind-Value="@this.dataSourcePort" Variant="Variant.Outlined" Margin="Margin.Dense" OnKeyUp="() => this.DataSourcePortWasTyped()"/>
</MudStack>
@if (this.dataSourcePort < 1024)
{
@ -168,7 +168,7 @@ else
<MudStack Row="@false" Spacing="1" Class="mb-1">
<MudSelectExtended
T="Auth"
Disabled="@this.IsNoneERIServerSelected"
Disabled="@this.IsERIInputDisabled"
ShrinkLabel="@true"
MultiSelection="@true"
MultiSelectionTextFunc="@this.GetMultiSelectionAuthText"
@ -185,12 +185,12 @@ else
</MudSelectItemExtended>
}
</MudSelectExtended>
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.authDescription" Label="@this.AuthDescriptionTitle()" Validation="@this.ValidateAuthDescription" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudTextField Disabled="@this.IsERIInputDisabled" T="string" @bind-Text="@this.authDescription" Label="@this.AuthDescriptionTitle()" Validation="@this.ValidateAuthDescription" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
</MudStack>
@if (this.selectedAuthenticationMethods.Contains(Auth.KERBEROS))
{
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="OperatingSystem" @bind-Value="@this.selectedOperatingSystem" Label="@T("Operating system on which your ERI will run")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateOperatingSystem" Class="mb-1">
<MudSelect Disabled="@this.IsERIInputDisabled" T="OperatingSystem" @bind-Value="@this.selectedOperatingSystem" Label="@T("Operating system on which your ERI will run")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateOperatingSystem" Class="mb-1">
@foreach (var os in Enum.GetValues<OperatingSystem>())
{
<MudSelectItem Value="@os">
@ -204,7 +204,7 @@ else
@T("Data protection settings")
</MudText>
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="AllowedLLMProviders" @bind-Value="@this.allowedLLMProviders" Label="@T("Allowed LLM providers for this data source")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateAllowedLLMProviders" Class="mb-1">
<MudSelect Disabled="@this.IsERIInputDisabled" T="AllowedLLMProviders" @bind-Value="@this.allowedLLMProviders" Label="@T("Allowed LLM providers for this data source")" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateAllowedLLMProviders" Class="mb-1">
@foreach (var option in Enum.GetValues<AllowedLLMProviders>())
{
<MudSelectItem Value="@option">
@ -227,7 +227,7 @@ else
@if (!this.IsNoneERIServerSelected)
{
<MudTable Items="@this.embeddings" Hover="@true" Class="border-dashed border rounded-lg">
<MudTable Items="@this.EmbeddingRows" Hover="@true" Class="border-dashed border rounded-lg">
<ColGroup>
<col/>
<col style="width: 34em;"/>
@ -243,10 +243,10 @@ else
<MudTd>@context.EmbeddingType</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditEmbedding(context)">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditEmbedding(context)" Disabled="@this.IsProcessing">
@T("Edit")
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteEmbedding(context)">
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteEmbedding(context)" Disabled="@this.IsProcessing">
@T("Delete")
</MudButton>
</MudStack>
@ -262,7 +262,7 @@ else
}
}
<MudButton Disabled="@this.IsNoneERIServerSelected" Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddEmbedding">
<MudButton Disabled="@this.IsERIInputDisabled" Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddEmbedding">
@T("Add Embedding Method")
</MudButton>
@ -276,7 +276,7 @@ else
@if (!this.IsNoneERIServerSelected)
{
<MudTable Items="@this.retrievalProcesses" Hover="@true" Class="border-dashed border rounded-lg">
<MudTable Items="@this.RetrievalProcessRows" Hover="@true" Class="border-dashed border rounded-lg">
<ColGroup>
<col/>
<col style="width: 34em;"/>
@ -289,10 +289,10 @@ else
<MudTd>@context.Name</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditRetrievalProcess(context)">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditRetrievalProcess(context)" Disabled="@this.IsProcessing">
@T("Edit")
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteRetrievalProcess(context)">
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteRetrievalProcess(context)" Disabled="@this.IsProcessing">
@T("Delete")
</MudButton>
</MudStack>
@ -308,7 +308,7 @@ else
}
}
<MudButton Disabled="@this.IsNoneERIServerSelected" Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddRetrievalProcess">
<MudButton Disabled="@this.IsERIInputDisabled" Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddRetrievalProcess">
@T("Add Retrieval Process")
</MudButton>
@ -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.")
</MudJustifiedText>
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.additionalLibraries" Label="@T("(Optional) Additional libraries")" HelperText="@T("Do you want to include additional libraries? Then name them and briefly describe what you want to achieve with them.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="12" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudTextField Disabled="@this.IsERIInputDisabled" T="string" @bind-Text="@this.additionalLibraries" Label="@T("(Optional) Additional libraries")" HelperText="@T("Do you want to include additional libraries? Then name them and briefly describe what you want to achieve with them.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="12" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudText Typo="Typo.h4" Class="mt-9 mb-1">
@T("Provider selection for generation")
@ -330,7 +330,7 @@ else
<b>@T("Important:")</b> @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.") <b>@T("However, generating all the files takes a certain amount of time.")</b> @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.")
</MudJustifiedText>
<ProviderSelection @bind-ProviderSettings="@this.ProviderSettings" ValidateProvider="@this.ValidatingProvider"/>
<ProviderSelection @bind-ProviderSettings="@this.ProviderSettings" ValidateProvider="@this.ValidatingProvider" Disabled="@this.IsProcessing"/>
<MudText Typo="Typo.h4" Class="mt-9 mb-1">
@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.") <b>@T("But beware:")</b> @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.")
</MudJustifiedText>
<MudTextSwitch Label="@T("Should we write the generated code to the file system?")" Disabled="@this.IsNoneERIServerSelected" @bind-Value="@this.writeToFilesystem" LabelOn="@T("Yes, please write or update all generated code to the file system")" LabelOff="@T("No, just show me the code")" />
<SelectDirectory Label="@T("Base directory where to write the code")" @bind-Directory="@this.baseDirectory" Disabled="@(this.IsNoneERIServerSelected || !this.writeToFilesystem)" DirectoryDialogTitle="@T("Select the target directory for the ERI server")" Validation="@this.ValidateDirectory" />
<MudTextSwitch Label="@T("Should we write the generated code to the file system?")" Disabled="@this.IsERIInputDisabled" @bind-Value="@this.writeToFilesystem" LabelOn="@T("Yes, please write or update all generated code to the file system")" LabelOff="@T("No, just show me the code")" />
<SelectDirectory Label="@T("Base directory where to write the code")" @bind-Directory="@this.baseDirectory" Disabled="@this.IsBaseDirectorySelectionDisabled" DirectoryDialogTitle="@T("Select the target directory for the ERI server")" Validation="@this.ValidateDirectory" />

View File

@ -534,15 +534,26 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
}
private bool AreServerPresetsBlocked => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions;
/// <summary>
/// Gets whether ERI server preset controls should be disabled.
/// </summary>
private bool AreServerPresetControlsDisabled => this.AreServerPresetsBlocked || this.IsProcessing;
private void SelectedERIServerChanged(DataERIServer? server)
{
if (this.IsProcessing)
return;
this.selectedERIServer = server;
this.ResetForm();
}
private async Task AddERIServer()
{
if (this.IsProcessing)
return;
this.SettingsManager.ConfigurationData.ERI.ERIServers.Add(new ()
{
ServerName = string.Format(T("ERI Server {0}"), DateTimeOffset.UtcNow),
@ -553,6 +564,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private async Task RemoveERIServer()
{
if (this.IsProcessing)
return;
if(this.selectedERIServer is null)
return;
@ -576,6 +590,31 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private bool IsNoneERIServerSelected => this.selectedERIServer is null;
/// <summary>
/// Gets whether ERI configuration input controls should be disabled.
/// </summary>
private bool IsERIInputDisabled => this.IsNoneERIServerSelected || this.IsProcessing;
/// <summary>
/// Gets whether the selected ERI specification cannot be downloaded.
/// </summary>
private bool IsSpecificationDownloadDisabled => !this.selectedERIVersion.WasSpecificationSelected() || this.IsERIInputDisabled;
/// <summary>
/// Gets whether the generated-code target directory selection should be disabled.
/// </summary>
private bool IsBaseDirectorySelectionDisabled => this.IsERIInputDisabled || !this.writeToFilesystem;
/// <summary>
/// Gets a stable row snapshot for the embedding-method table.
/// </summary>
private EmbeddingInfo[] EmbeddingRows => this.embeddings.ToArray();
/// <summary>
/// Gets a stable row snapshot for the retrieval-process table.
/// </summary>
private RetrievalInfo[] RetrievalProcessRows => this.retrievalProcesses.ToArray();
/// <summary>
/// Gets called when the server name was changed by typing.
/// </summary>
@ -863,6 +902,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private async Task AddEmbedding()
{
if (this.IsProcessing)
return;
var dialogParameters = new DialogParameters<EmbeddingMethodDialog>
{
{ x => x.IsEditing, false },
@ -881,6 +923,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private async Task EditEmbedding(EmbeddingInfo embeddingInfo)
{
if (this.IsProcessing)
return;
var dialogParameters = new DialogParameters<EmbeddingMethodDialog>
{
{ x => x.DataEmbeddingName, embeddingInfo.EmbeddingName },
@ -906,6 +951,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private async Task DeleteEmbedding(EmbeddingInfo embeddingInfo)
{
if (this.IsProcessing)
return;
var message = this.retrievalProcesses.Any(n => n.Embeddings?.Contains(embeddingInfo) is true)
? string.Format(T("The embedding '{0}' is used in one or more retrieval processes. Are you sure you want to delete it?"), embeddingInfo.EmbeddingName)
: string.Format(T("Are you sure you want to delete the embedding '{0}'?"), embeddingInfo.EmbeddingName);
@ -928,6 +976,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private async Task AddRetrievalProcess()
{
if (this.IsProcessing)
return;
var dialogParameters = new DialogParameters<RetrievalProcessDialog>
{
{ x => x.IsEditing, false },
@ -947,6 +998,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private async Task EditRetrievalProcess(RetrievalInfo retrievalInfo)
{
if (this.IsProcessing)
return;
var dialogParameters = new DialogParameters<RetrievalProcessDialog>
{
{ x => x.DataName, retrievalInfo.Name },
@ -973,6 +1027,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private async Task DeleteRetrievalProcess(RetrievalInfo retrievalInfo)
{
if (this.IsProcessing)
return;
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{ x => x.Message, string.Format(T("Are you sure you want to delete the retrieval process '{0}'?"), retrievalInfo.Name) },
@ -1032,6 +1089,10 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
this.AddInputIssue(T("Please describe at least one retrieval process."));
return;
}
var writeToFilesystemSnapshot = this.writeToFilesystem;
var baseDirectorySnapshot = this.baseDirectory;
var previouslyGeneratedFilesSnapshot = this.previouslyGeneratedFiles.ToArray();
this.eriSpecification = await this.selectedERIVersion.ReadSpecification(this.HttpClient);
if (string.IsNullOrWhiteSpace(this.eriSpecification))
@ -1073,9 +1134,9 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
var fileListAnswer = await this.AddAIResponseAsync(time, true);
// Is this an update of the ERI server? If so, we need to delete the previously generated files:
if (this.writeToFilesystem && this.previouslyGeneratedFiles.Count > 0 && !string.IsNullOrWhiteSpace(fileListAnswer))
if (writeToFilesystemSnapshot && previouslyGeneratedFilesSnapshot.Length > 0 && !string.IsNullOrWhiteSpace(fileListAnswer))
{
foreach (var file in this.previouslyGeneratedFiles)
foreach (var file in previouslyGeneratedFilesSnapshot)
{
try
{
@ -1097,7 +1158,8 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
}
var generatedFiles = new List<string>();
foreach (var file in this.ExtractFiles(fileListAnswer))
var filesToGenerate = this.ExtractFiles(fileListAnswer).ToArray();
foreach (var file in filesToGenerate)
{
this.Logger.LogInformation($"The LLM want to create the file: '{file}'");
@ -1117,15 +1179,15 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
```
""", true);
var generatedCodeMarkdown = await this.AddAIResponseAsync(time);
if (this.writeToFilesystem)
if (writeToFilesystemSnapshot)
{
var desiredFilePath = Path.Join(this.baseDirectory, file);
var desiredFilePath = Path.Join(baseDirectorySnapshot, file);
// Security check: ensure that the desired file path is inside the base directory.
// We cannot trust the beginning of the file path because it would be possible
// to escape by using `..` in the file path.
if (!desiredFilePath.StartsWith(this.baseDirectory, StringComparison.InvariantCultureIgnoreCase) || desiredFilePath.Contains(".."))
this.Logger.LogWarning($"The file path '{desiredFilePath}' is may not inside the base directory '{this.baseDirectory}'.");
if (!desiredFilePath.StartsWith(baseDirectorySnapshot, StringComparison.InvariantCultureIgnoreCase) || desiredFilePath.Contains(".."))
this.Logger.LogWarning($"The file path '{desiredFilePath}' is may not inside the base directory '{baseDirectorySnapshot}'.");
else
{
@ -1160,7 +1222,7 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
}
}
if(this.writeToFilesystem)
if(writeToFilesystemSnapshot)
{
this.previouslyGeneratedFiles = generatedFiles;
this.selectedERIServer!.PreviouslyGeneratedFiles = generatedFiles;