Merge branch 'main' into chunk-data

Resolved 29 conflicting files. The notable decisions:

Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.

Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.

File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.

Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.

Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.

.NET and Rust both build clean; I18N regenerated to 4060 keys.
This commit is contained in:
Thorsten Sommer committed 2026-09-05 21:20:33 +02:00
commit fe35630eff
640 files changed
+45059 -4952

No files matched your search

@@ -36,6 +36,7 @@
<ConfigurationProviderSelection Component="Components.AGENDA_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider = selectedValue)"/>
<ConfigurationSelect OptionDescription="@T("Preselect a profile")" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => ProfilePreselection.FromStoredValue(this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile))" Data="@ConfigurationSelectDataFactory.GetComponentProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile = selectedValue)" OptionHelp="@T("Choose whether the assistant should use the app default profile, no profile, or a specific profile.")"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.AGENDA_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@@ -32,6 +32,7 @@
<ConfigurationProviderSelection Component="Components.BIAS_DAY_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider = selectedValue)"/>
</MudPaper>
</MudField>
<ToolDefaultsConfiguration Component="Components.BIAS_DAY_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@@ -1,5 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Tools.Services;
@@ -40,18 +38,17 @@ public abstract class SettingsDialogBase : MSGComponentBase
protected void Close() => this.MudDialog.Cancel();
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
private void UpdateProviders()
{
this.AvailableLLMProviders.Clear();
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
foreach (var provider in this.SettingsManager.GetAllProviders())
this.AvailableLLMProviders.Add(new (provider.InstanceName, provider.Id));
}
private void UpdateEmbeddingProviders()
{
this.AvailableEmbeddingProviders.Clear();
foreach (var provider in this.SettingsManager.ConfigurationData.EmbeddingProviders)
foreach (var provider in this.SettingsManager.GetAllEmbeddingProviders())
this.AvailableEmbeddingProviders.Add(new (provider.Name, provider.Id));
}
@@ -0,0 +1,93 @@
@using AIStudio.Assistants.BatchProcessing
@using AIStudio.Settings
@using AIStudio.Settings.DataModel
@using AIStudio.Tools.Rust
@inherits SettingsDialogBase
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6" Class="d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.BatchPrediction" Class="mr-2"/>
@T("Assistant: Batch Processing defaults")
</MudText>
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="@T("Preselect batch processing options?")" LabelOn="@T("Batch processing options are preselected")" LabelOff="@T("No batch processing options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.PreselectOptions)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.PreselectOptions = value)" OptionHelp="@T("When enabled, new batch runs start with the defaults configured below.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PreselectOptions, out var meta) && meta.IsLocked"/>
<MudText Typo="Typo.h6" Class="mb-3">@T("Input")</MudText>
<ConfigurationDirectory OptionDescription="@T("Default input folder")" Disabled="@this.DefaultsDisabled" DirectoryDialogTitle="@T("Select the default input folder")" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory = value)" OptionHelp="@T("Leave empty when an input folder should be selected for every batch run.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.InputDirectory, out var meta) && meta.IsLocked"/>
<ConfigurationText OptionDescription="@T("Default file patterns")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.FilterAlt" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns = value)" ResetValue="@(() => DataBatchProcessing.DEFAULT_FILE_PATTERNS)" ResetButtonText="@T("Restore default patterns")" OptionHelp="@T("Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.FilePatterns, out var meta) && meta.IsLocked"/>
<ConfigurationOption OptionDescription="@T("Include subfolders by default?")" Disabled="@this.DefaultsDisabled" LabelOn="@T("Subfolders are included")" LabelOff="@T("Only the selected folder is processed")" State="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.IncludeSubdirectories, out var meta) && meta.IsLocked"/>
<MudText Typo="Typo.h6" Class="mb-3 mt-6">@T("Instructions")</MudText>
<ConfigurationSelect OptionDescription="@T("Default source of the instructions")" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource)" Data="@this.PromptSourceData" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PromptSource, out var meta) && meta.IsLocked"/>
@if (this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource is BatchProcessingPromptSource.FREE_PROMPT)
{
<ReadFileContent Text="@T("Load default prompt from file")" FileContent="@this.SettingsManager.ConfigurationData.BatchProcessing.FreePrompt" FileContentChanged="@this.UpdateFreePromptFromFileAsync" EnableDragDrop="true" Layer="@DropLayers.DIALOGS" CatchAllDocuments="true" Disabled="@this.FreePromptImportDisabled()"/>
<ConfigurationText OptionDescription="@T("Default prompt")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.EditNote" NumLines="5" MaxLines="26" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.FreePrompt)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.FreePrompt = value)" OptionHelp="@T("These instructions are applied to every document of a new batch run.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.FreePrompt, out var meta) && meta.IsLocked"/>
}
else if (this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource is BatchProcessingPromptSource.FILE_IMPORT)
{
<ReadFileContent Text="@T("Load default Markdown instructions file")" Filter="@([FileTypes.MARKDOWN])" FilePathLoaded="@this.UpdatePromptFilePathAsync" EnableDragDrop="true" Layer="@DropLayers.DIALOGS" CatchAllDocuments="true" Disabled="@this.PromptFileImportDisabled()"/>
<ConfigurationFile OptionDescription="@T("Default Markdown instructions file")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Description" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.PromptFilePath)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.PromptFilePath = value)" FileDialogTitle="@T("Select the default Markdown instructions file")" Filter="@([FileTypes.MARKDOWN])" OptionHelp="@T("The current content of this Markdown file is loaded whenever the defaults are applied.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PromptFilePath, out var meta) && meta.IsLocked"/>
}
else
{
<ConfigurationSelect OptionDescription="@T("Default document analysis policy")" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.PreselectedPolicyId)" Data="@this.PolicyData" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.PreselectedPolicyId = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PreselectedPolicyId, out var meta) && meta.IsLocked"/>
@if (this.SelectedPolicyMissing)
{
<MudAlert Severity="Severity.Warning" Dense="true" Class="mb-6">@T("The configured default policy no longer exists. Select another policy before starting a policy-based batch run.")</MudAlert>
}
}
@* The tools belong to the instructions, which is why they sit here rather than at the end of the dialog. *@
@if (this.SettingsManager.ConfigurationData.BatchProcessing.PromptSource is BatchProcessingPromptSource.POLICY)
{
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("A policy brings its own tools, so there is nothing to preselect here. You configure them with the policy in the Document Analysis Assistant.")
</MudJustifiedText>
}
else
{
<ToolDefaultsConfiguration Component="Components.BATCH_PROCESSING_ASSISTANT" IncludeVisibilityToggle="@false" />
}
<MudText Typo="Typo.h6" Class="mb-3 mt-6">@T("Output")</MudText>
<ConfigurationSelect OptionDescription="@T("Default output mode")" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.OutputMode)" Data="@this.OutputModeData" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.OutputMode = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.OutputMode, out var meta) && meta.IsLocked"/>
@if (this.SettingsManager.ConfigurationData.BatchProcessing.OutputMode is BatchProcessingOutputMode.INDIVIDUAL_FILES)
{
<ConfigurationSelect OptionDescription="@T("Default file format")" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.ResultFileFormat)" Data="@ResultFileFormatData" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.ResultFileFormat = value)" OptionHelp="@T("Choose the format of new result files. Everything except Markdown is converted by Pandoc.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.ResultFileFormat, out var meta) && meta.IsLocked"/>
}
else
{
<ConfigurationText OptionDescription="@T("Default results table name")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Description" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.CsvFileName)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.CsvFileName = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.CsvFileName, out var meta) && meta.IsLocked"/>
<ConfigurationText OptionDescription="@T("Default result column header")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.TableChart" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.ResultColumnHeader)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.ResultColumnHeader = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.ResultColumnHeader, out var meta) && meta.IsLocked"/>
<ConfigurationSelect OptionDescription="@T("Default column separator")" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.CsvSeparator)" Data="@this.CsvSeparatorData" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.CsvSeparator = value)" OptionHelp="@T("Choose which character separates the columns of new results tables.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.CsvSeparator, out var meta) && meta.IsLocked"/>
@if (this.SettingsManager.ConfigurationData.BatchProcessing.CsvSeparator is BatchProcessingCsvSeparator.CUSTOM)
{
<ConfigurationText OptionDescription="@T("Default custom column separator")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Edit" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.CustomCsvSeparator)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.CustomCsvSeparator = value)" Validation="@this.ValidateCustomCsvSeparator" OptionHelp="@T("Enter one punctuation or symbol character.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.CustomCsvSeparator, out var meta) && meta.IsLocked"/>
}
}
<ConfigurationDirectory OptionDescription="@T("Default output folder")" Disabled="@this.DefaultsDisabled" DirectoryDialogTitle="@T("Select the default output folder")" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.OutputDirectory)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.OutputDirectory = value)" OptionHelp="@T("Leave empty to use the ai-results subfolder of the input folder.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.OutputDirectory, out var meta) && meta.IsLocked"/>
<MudText Typo="Typo.h6" Class="mb-3 mt-6">@T("Processing pace")</MudText>
@if (this.MinimumDelayIsManaged)
{
<MudAlert Severity="Severity.Info" Dense="true" Class="mb-6">@(string.Format(T("Your organization requires a pause of at least {0} seconds between files. Users can configure only the upper limit."), this.ManagedMinimumDelaySeconds))</MudAlert>
}
else
{
<ConfigurationSlider T="int" OptionDescription="@T("Default minimum pause between files")" Min="@DataBatchProcessing.MIN_DELAY_SECONDS" Max="@DataBatchProcessing.MAX_DELAY_SECONDS" Step="1" Unit="@T("seconds")" Disabled="@this.DefaultsDisabled" Value="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.MinimumDelaySeconds)" ValueUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.MinimumDelaySeconds = value)" OptionHelp="@T("The lower end of the random pause interval. AI Studio never allows less than 6 seconds.")"/>
}
<ConfigurationSlider T="int" OptionDescription="@T("Default maximum pause between files")" Min="@this.EffectiveMinimumDelaySeconds" Max="@DataBatchProcessing.MAX_DELAY_SECONDS" Step="1" Unit="@T("seconds")" Disabled="@this.DefaultsDisabled" Value="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.MaximumDelaySeconds)" ValueUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.MaximumDelaySeconds = value)" OptionHelp="@T("The upper end of the random pause interval. The app-wide maximum is 300 seconds (5 minutes).")"/>
<MudText Typo="Typo.h6" Class="mb-3 mt-6">@T("AI selection")</MudText>
<ConfigurationMinConfidenceSelection Disabled="@this.DefaultsDisabled" RestrictToGlobalMinimumConfidence="true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.MinimumProviderConfidence)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.MinimumProviderConfidence = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.MinimumProviderConfidence, out var meta) && meta.IsLocked"/>
<ConfigurationProviderSelection Component="Components.BATCH_PROCESSING_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.PreselectedProvider)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.PreselectedProvider = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PreselectedProvider, out var meta) && meta.IsLocked"/>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">@T("Close")</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,109 @@
using AIStudio.Assistants.BatchProcessing;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
namespace AIStudio.Dialogs.Settings;
public partial class SettingsDialogBatchProcessing : SettingsDialogBase
{
private bool DefaultsDisabled() => !this.SettingsManager.ConfigurationData.BatchProcessing.PreselectOptions;
private bool MinimumDelayIsManaged => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.MinimumDelaySeconds, out var meta)
&& meta.ManagedMode is not null;
private int ManagedMinimumDelaySeconds => Math.Clamp(
this.SettingsManager.ConfigurationData.BatchProcessing.MinimumDelaySeconds,
DataBatchProcessing.MIN_DELAY_SECONDS,
DataBatchProcessing.MAX_DELAY_SECONDS);
private int EffectiveMinimumDelaySeconds => this.MinimumDelayIsManaged
? this.ManagedMinimumDelaySeconds
: Math.Clamp(
this.SettingsManager.ConfigurationData.BatchProcessing.MinimumDelaySeconds,
DataBatchProcessing.MIN_DELAY_SECONDS,
DataBatchProcessing.MAX_DELAY_SECONDS);
private bool FreePromptImportDisabled() => this.DefaultsDisabled()
|| ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.FreePrompt, out var meta) && meta.IsLocked;
private bool PromptFileImportDisabled() => this.DefaultsDisabled()
|| ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.PromptFilePath, out var meta) && meta.IsLocked;
private async Task UpdateFreePromptFromFileAsync(string content)
{
this.SettingsManager.ConfigurationData.BatchProcessing.FreePrompt = content;
await this.StoreImportedDefaultAsync();
}
private async Task UpdatePromptFilePathAsync(string path)
{
this.SettingsManager.ConfigurationData.BatchProcessing.PromptFilePath = path;
await this.StoreImportedDefaultAsync();
}
private async Task StoreImportedDefaultAsync()
{
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private IReadOnlyList<ConfigurationSelectData<BatchProcessingPromptSource>> PromptSourceData =>
[
.. Enum
.GetValues<BatchProcessingPromptSource>()
.Select(value => new ConfigurationSelectData<BatchProcessingPromptSource>(value.Name(), value))
];
private IReadOnlyList<ConfigurationSelectData<BatchProcessingOutputMode>> OutputModeData =>
[
.. Enum
.GetValues<BatchProcessingOutputMode>()
.Select(value => new ConfigurationSelectData<BatchProcessingOutputMode>(value.Name(), value))
];
private static IReadOnlyList<ConfigurationSelectData<FileExportFormat>> ResultFileFormatData =>
[
.. FileExportFormatExtensions.ANSWER_FORMATS
.Select(value => new ConfigurationSelectData<FileExportFormat>(value.ToName(), value))
];
private IReadOnlyList<ConfigurationSelectData<BatchProcessingCsvSeparator>> CsvSeparatorData =>
[
.. Enum
.GetValues<BatchProcessingCsvSeparator>()
.Select(value => new ConfigurationSelectData<BatchProcessingCsvSeparator>(value.Name(), value))
];
private string? ValidateCustomCsvSeparator(string separator)
{
if (!BatchProcessingCsvSeparatorExtensions.IsValidCustomSeparator(separator))
return T("Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.");
return null;
}
private IReadOnlyList<ConfigurationSelectData<string>> PolicyData
{
get
{
var selectedPolicyId = this.SettingsManager.ConfigurationData.BatchProcessing.PreselectedPolicyId;
var policies = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies
.Select(policy => new ConfigurationSelectData<string>(policy.PolicyName, policy.Id))
.ToList();
if (this.SelectedPolicyMissing)
policies.Add(new(string.Format(T("Missing policy ({0})"), selectedPolicyId), selectedPolicyId));
return policies;
}
}
private bool SelectedPolicyMissing
{
get
{
var selectedPolicyId = this.SettingsManager.ConfigurationData.BatchProcessing.PreselectedPolicyId;
return !string.IsNullOrWhiteSpace(selectedPolicyId) && this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.All(policy => policy.Id != selectedPolicyId);
}
}
}
@@ -14,7 +14,6 @@
<ConfigurationOption OptionDescription="@T("Show the latest message after loading?")" LabelOn="@T("Latest message is shown, after loading a chat")" LabelOff="@T("First (oldest) message is shown, after loading a chat")" State="@(() => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading = updatedState)" OptionHelp="@T("When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown.")"/>
<ConfigurationSelect OptionDescription="@T("Provider selection when creating new chats")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior)" Data="@ConfigurationSelectDataFactory.GetAddChatProviderBehavior()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior = selectedValue)" OptionHelp="@T("Control how the LLM provider for added chats is selected.")"/>
<ConfigurationSelect OptionDescription="@T("Provider selection when loading a chat and sending assistant results to chat")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)" Data="@ConfigurationSelectDataFactory.GetLoadingChatProviderBehavior()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior = selectedValue)" OptionHelp="@T("Control how the LLM provider for loaded chats is selected and when assistant results are sent to chat.")"/>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="@T("Preselect chat options?")" LabelOn="@T("Chat options are preselected")" LabelOff="@T("No chat options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectOptions, out var meta) && meta.IsLocked"/>
<ConfigurationProviderSelection Component="Components.CHAT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)" IsLocked="() => ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedProvider, out var meta) && meta.IsLocked"/>
@@ -22,6 +21,8 @@
<ConfigurationSelect OptionDescription="@T("Preselect one of your chat templates?")" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedChatTemplate)" Data="@ConfigurationSelectDataFactory.GetChatTemplatesData(this.SettingsManager.ConfigurationData.ChatTemplates)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedChatTemplate = selectedValue)" OptionHelp="@T("Would you like to set one of your chat templates as the default for chats?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedChatTemplate, out var meta) && meta.IsLocked"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.CHAT" IncludeVisibilityToggle="@false" />
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
<DataSourceSelection SelectionMode="DataSourceSelectionMode.CONFIGURATION_MODE" AutoSaveAppSettings="@true" @bind-DataSourceOptions="@this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions" ConfigurationHeaderMessage="@T("You can set default data sources and options for new chats. You can change these settings later for each individual chat.")"/>
@@ -48,27 +48,22 @@
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditChatTemplate(context))"/>
</MudTooltip>
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
@if (context.FileAttachments.Count == 0)
{
@if (context.FileAttachments.Count == 0)
{
<MudTooltip Text="@T("Export configuration")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportChatTemplateWithSharedAttachmentPaths(context))"/>
</MudTooltip>
}
else
{
<MudTooltip Text="@T("Export configuration")">
<MudMenu Icon="@Icons.Material.Filled.Dataset" Color="Color.Info" Variant="Variant.Text">
<MudMenuItem Icon="@Icons.Material.Filled.Link" OnClick="@(() => this.ExportChatTemplateWithSharedAttachmentPaths(context))">
@T("Use shared attachment paths")
</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Folder" Disabled="@this.isPluginDirectoryDialogOpen" OnClick="@(() => this.ExportChatTemplateWithPackagedAttachments(context))">
@T("Copy attachments into plugin")
</MudMenuItem>
</MudMenu>
</MudTooltip>
}
<AdminExportButton OnClick="@(() => this.ExportChatTemplateWithSharedAttachmentPaths(context))" />
}
else if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
{
<MudTooltip Text="@T("Export configuration")">
<MudMenu Icon="@Icons.Material.Filled.Dataset" Color="Color.Info" Variant="Variant.Text">
<MudMenuItem Icon="@Icons.Material.Filled.Link" OnClick="@(() => this.ExportChatTemplateWithSharedAttachmentPaths(context))">
@T("Use shared attachment paths")
</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Folder" Disabled="@this.isPluginDirectoryDialogOpen" OnClick="@(() => this.ExportChatTemplateWithPackagedAttachments(context))">
@T("Copy attachments into plugin")
</MudMenuItem>
</MudMenu>
</MudTooltip>
}
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteChatTemplate(context))"/>
@@ -16,6 +16,7 @@
<ConfigurationProviderSelection Component="Components.CODING_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider = selectedValue)"/>
<ConfigurationSelect OptionDescription="@T("Preselect a profile")" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => ProfilePreselection.FromStoredValue(this.SettingsManager.ConfigurationData.Coding.PreselectedProfile))" Data="@ConfigurationSelectDataFactory.GetComponentProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProfile = selectedValue)" OptionHelp="@T("Choose whether the assistant should use the app default profile, no profile, or a specific profile.")"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.CODING_ASSISTANT" IncludeVisibilityToggle="@false" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">Close</MudButton>
@@ -81,11 +81,9 @@
<MudTooltip Text="@T("Refresh")">
<MudIconButton Color="Color.Primary" Icon="@Icons.Material.Filled.Sync" Disabled="@(!this.CanRefreshDataSource(context))" OnClick="() => this.RefreshDataSource(context)"/>
</MudTooltip>
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings && context is DataSourceERI_V1)
@if (context is DataSourceERI_V1)
{
<MudTooltip Text="@T("Export configuration")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="() => this.ExportDataSource(context)"/>
</MudTooltip>
<AdminExportButton OnClick="@(() => this.ExportDataSource(context))" />
}
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteDataSource(context)"/>
@@ -19,10 +19,11 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.GRAMMAR_SPELLING_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.GRAMMAR_SPELLING_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -19,10 +19,11 @@
<ConfigurationSelect OptionDescription="@T("Language plugin used for comparision")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectedLanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.I18N.PreselectedLanguagePluginId = selectedValue)" OptionHelp="@T("Select the language plugin used for comparision.")"/>
<ConfigurationProviderSelection Component="Components.I18N_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.I18N.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.I18N.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.I18N_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -15,10 +15,11 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.ICON_FINDER_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.ICON_FINDER_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -26,10 +26,11 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.JOB_POSTING_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.JOB_POSTING_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -17,6 +17,7 @@
<ConfigurationProviderSelection Component="Components.LEGAL_CHECK_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider = selectedValue)"/>
<ConfigurationSelect OptionDescription="@T("Preselect a profile")" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => ProfilePreselection.FromStoredValue(this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile))" Data="@ConfigurationSelectDataFactory.GetComponentProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile = selectedValue)" OptionHelp="@T("Choose whether the assistant should use the app default profile, no profile, or a specific profile.")"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.LEGAL_CHECK_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@@ -20,6 +20,7 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.MY_TASKS_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.MY_TASKS_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@@ -37,7 +37,7 @@
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
</MudTooltip>
<MudTooltip Text="@T("View")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Visibility" OnClick="() => this.ViewProfile(context)"/>
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Visibility" OnClick="@(() => this.ViewProfile(context))"/>
</MudTooltip>
</MudStack>
}
@@ -45,16 +45,11 @@
{
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditProfile(context)"/>
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditProfile(context))"/>
</MudTooltip>
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
{
<MudTooltip Text="@T("Export configuration")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="() => this.ExportProfile(context)"/>
</MudTooltip>
}
<AdminExportButton OnClick="@(() => this.ExportProfile(context))" />
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteProfile(context)"/>
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteProfile(context))"/>
</MudTooltip>
</MudStack>
}
@@ -21,10 +21,11 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.REWRITE_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.REWRITE_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -25,6 +25,7 @@
<ConfigurationProviderSelection Component="Components.SLIDE_BUILDER_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.SlideBuilder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.SlideBuilder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.SlideBuilder.PreselectedProvider = selectedValue)"/>
<ConfigurationSelect OptionDescription="@T("Preselect a profile")" Disabled="@(() => !this.SettingsManager.ConfigurationData.SlideBuilder.PreselectOptions)" SelectedValue="@(() => ProfilePreselection.FromStoredValue(this.SettingsManager.ConfigurationData.SlideBuilder.PreselectedProfile))" Data="@ConfigurationSelectDataFactory.GetComponentProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.SlideBuilder.PreselectedProfile = selectedValue)" OptionHelp="@T("Choose whether the assistant should use the app default profile, no profile, or a specific profile.")"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.SLIDE_BUILDER_ASSISTANT" IncludeVisibilityToggle="@false" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@@ -19,10 +19,11 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.SYNONYMS_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.SYNONYMS_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -29,10 +29,11 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.TEXT_SUMMARIZER_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.TEXT_SUMMARIZER_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -23,10 +23,11 @@
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.TRANSLATION_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider = selectedValue)"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.TRANSLATION_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -23,6 +23,7 @@
<ConfigurationProviderSelection Component="Components.EMAIL_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider = selectedValue)"/>
<ConfigurationSelect OptionDescription="@T("Preselect a profile")" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => ProfilePreselection.FromStoredValue(this.SettingsManager.ConfigurationData.EMail.PreselectedProfile))" Data="@ConfigurationSelectDataFactory.GetComponentProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProfile = selectedValue)" OptionHelp="@T("Choose whether the assistant should use the app default profile, no profile, or a specific profile.")"/>
</MudPaper>
<ToolDefaultsConfiguration Component="Components.EMAIL_ASSISTANT" />
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@@ -0,0 +1,88 @@
@inherits SettingsDialogBase
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6" Class="d-flex align-center">
<MudIcon Icon="@this.implementation?.Icon" Class="mr-2" />
@(this.implementation?.GetDisplayName() ?? T("Tool Settings"))
</MudText>
</TitleContent>
<DialogContent>
@if (this.toolDefinition is null)
{
<MudText Typo="Typo.body1">@T("The selected tool could not be loaded.")</MudText>
}
else
{
<MudJustifiedText Typo="Typo.body1" Class="mb-4">
@this.implementation?.GetDescription()
</MudJustifiedText>
@if (!this.SettingsManager.IsToolActive(this.toolDefinition.Id))
{
<MudAlert Severity="Severity.Warning" Class="mb-4">@T("This tool has been disabled by your organization.")</MudAlert>
}
@if (!string.IsNullOrWhiteSpace(this.validationMessage))
{
<MudAlert Severity="Severity.Error" Class="mb-4">@this.validationMessage</MudAlert>
}
@foreach (var warning in this.GetSettingsWarnings())
{
<MudAlert Severity="Severity.Warning" Class="mb-4">@warning</MudAlert>
}
@foreach (var group in this.BuildVisibleFieldGroups())
{
<MudPaper Class="pa-3 mb-4 border-dashed border rounded-lg">
@if (this.ShowsGroupHeader(group))
{
<MudStack Row="@true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap" Class="mb-3">
<MudText Typo="Typo.subtitle2">@this.GetGroupLabel(group.Key)</MudText>
<MudStack Row="@true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
@foreach (var link in this.GetGroupLinks(group.Key))
{
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@link.Icon" Href="@link.Url" Target="_blank">
@link.Label
</MudButton>
}
</MudStack>
</MudStack>
}
@foreach (var property in group.Fields)
{
var fieldName = property.Key;
var field = property.Value;
var fieldOptions = field.GetOptions();
if (fieldOptions.Count > 0)
{
<MudSelect T="string" Label="@this.GetFieldLabel(fieldName, field)" Value="@this.GetValue(fieldName)" ValueChanged="@(value => this.UpdateValue(fieldName, value))" Variant="Variant.Outlined" Margin="Margin.Dense" HelperText="@this.GetFieldDescription(fieldName, field)" Placeholder="@this.GetFieldPlaceholder(fieldName, field)" Class="mb-3" Disabled="@this.IsFieldDisabled(fieldName)">
@if (!this.toolDefinition.SettingsSchema.Required.Contains(fieldName))
{
<MudSelectItem T="string" Value="@string.Empty">@T("Not set")</MudSelectItem>
}
@foreach (var option in fieldOptions)
{
<MudSelectItem T="string" Value="@option.Value">@option.Label</MudSelectItem>
}
</MudSelect>
}
else
{
<MudTextField T="string" Label="@this.GetFieldLabel(fieldName, field)" Value="@this.GetValue(fieldName)" ValueChanged="@(value => this.UpdateValue(fieldName, value))" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3" HelperText="@this.GetFieldDescription(fieldName, field)" Placeholder="@this.GetFieldPlaceholder(fieldName, field)" InputType="@(field.Secret ? InputType.Password : InputType.Text)" Disabled="@this.IsFieldDisabled(fieldName)" />
}
}
</MudPaper>
}
}
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Text">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Save" Variant="Variant.Filled" Disabled="@(this.toolDefinition is null)">
@T("Save")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,183 @@
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs.Settings;
public partial class ToolSettingsDialog : SettingsDialogBase
{
[Parameter]
public string ToolId { get; set; } = string.Empty;
[Inject]
private ToolRegistry ToolRegistry { get; init; } = null!;
[Inject]
private ToolSettingsService ToolSettingsService { get; init; } = null!;
private ToolDefinition? toolDefinition;
private IToolImplementation? implementation;
private Dictionary<string, string> values = new(StringComparer.Ordinal);
private IReadOnlyList<FieldGroup> fieldGroups = [];
private string validationMessage = string.Empty;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
this.toolDefinition = this.ToolRegistry.GetDefinition(this.ToolId);
if (this.toolDefinition is not null)
{
this.implementation = this.ToolRegistry.GetImplementation(this.toolDefinition.ImplementationKey);
this.values = await this.ToolSettingsService.GetSettingsAsync(this.toolDefinition);
this.fieldGroups = BuildFieldGroups(this.toolDefinition);
}
}
private string GetValue(string fieldName) => this.values.GetValueOrDefault(fieldName, string.Empty);
/// <summary>
/// Splits the tool's settings fields into the groups the tool declared for them.
/// </summary>
/// <remarks>
/// Groups appear in the order in which their first field appears in the schema, and the
/// fields keep the order the tool wrote them in. That is the order the fields have always
/// been rendered in, so a tool without groups looks exactly as it did before: one group
/// with an empty name, holding everything.<br/><br/>
/// A schema does not change while the dialog is open, so this runs once rather than on
/// every render.
/// </remarks>
private static IReadOnlyList<FieldGroup> BuildFieldGroups(ToolDefinition definition)
{
var groups = new List<FieldGroup>();
var groupIndexByKey = new Dictionary<string, int>(StringComparer.Ordinal);
foreach (var property in definition.SettingsSchema.Properties)
{
if (!groupIndexByKey.TryGetValue(property.Value.Group, out var groupIndex))
{
groupIndex = groups.Count;
groupIndexByKey[property.Value.Group] = groupIndex;
groups.Add(new FieldGroup(property.Value.Group, []));
}
groups[groupIndex].Fields.Add(property);
}
return groups;
}
/// <summary>
/// The groups as they are rendered right now, without the fields the tool is hiding.
/// </summary>
/// <remarks>
/// Which fields make sense can depend on what is filled in, so this is built on every
/// render rather than once: a field the tool starts to offer has to appear as soon as the
/// value it depends on changes. A group whose every field is hidden is left out entirely,
/// so no empty box is rendered.<br/><br/>
/// Cheap enough to be called more than once per render: a tool has a handful of settings,
/// and asking the tool about one of them costs a dictionary lookup or two.
/// </remarks>
private IReadOnlyList<FieldGroup> BuildVisibleFieldGroups()
{
if (this.implementation is null)
return this.fieldGroups;
var visibleGroups = new List<FieldGroup>();
foreach (var group in this.fieldGroups)
{
var visibleFields = group.Fields.Where(field => this.implementation.IsSettingsFieldVisible(field.Key, this.values)).ToList();
if (visibleFields.Count > 0)
visibleGroups.Add(new FieldGroup(group.Key, visibleFields));
}
return visibleGroups;
}
/// <summary>
/// Whether one group shows a heading above its fields.
/// </summary>
/// <remarks>
/// A tool that declares no groups has a single nameless group holding everything, and a
/// heading above the only box would say nothing the dialog's title does not say already.
/// As soon as there is a second box, each of them has to state which one it is — the box
/// holding the fields that belong to no group in particular included.<br/><br/>
/// It counts the boxes that are actually rendered, so a group the tool hides entirely does
/// not leave the remaining box with a heading it does not need.
/// </remarks>
private bool ShowsGroupHeader(FieldGroup group) => this.BuildVisibleFieldGroups().Count > 1 || !string.IsNullOrEmpty(group.Key);
/// <remarks>
/// The ungrouped fields have no name of their own, so the label hook hands back their
/// empty group name. A tool may still name them through that same hook; when it does not,
/// they are simply what is left over next to the named groups.
/// </remarks>
private string GetGroupLabel(string groupKey)
{
var label = this.implementation?.GetSettingsGroupLabel(groupKey) ?? groupKey;
return string.IsNullOrEmpty(label) ? T("General") : label;
}
private IReadOnlyList<ToolSettingsGroupLink> GetGroupLinks(string groupKey) => this.implementation?.GetSettingsGroupLinks(groupKey) ?? [];
/// <summary>
/// What the tool wants to say about the settings as they stand right now.
/// </summary>
/// <remarks>
/// Asked on every render, so a warning follows the value it is about instead of waiting for
/// the next save. These are not errors: they describe settings that are allowed and do
/// something other than what they look like, and the dialog saves them either way.
/// </remarks>
private IReadOnlyList<string> GetSettingsWarnings() => this.implementation?.GetSettingsWarnings(this.values) ?? [];
private string GetFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
this.implementation?.GetSettingsFieldLabel(fieldName, fieldDefinition) ?? fieldDefinition.Title;
private string GetFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
this.GetFieldDescriptionWithDefault(fieldName, fieldDefinition);
private string GetFieldDefaultValue(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
this.implementation?.GetSettingsFieldDefaultValue(fieldName, fieldDefinition) ?? string.Empty;
private string GetFieldDescriptionWithDefault(string fieldName, ToolSettingsFieldDefinition fieldDefinition)
{
var description = this.implementation?.GetSettingsFieldDescription(fieldName, fieldDefinition) ?? fieldDefinition.Description;
var defaultValue = this.GetFieldDefaultValue(fieldName, fieldDefinition);
if (string.IsNullOrWhiteSpace(defaultValue))
return description;
return string.Format(T("{0} Default: {1}"), description, defaultValue);
}
private bool IsFieldDisabled(string fieldName) =>
this.toolDefinition is not null && this.ToolSettingsService.IsFieldLocked(this.toolDefinition, fieldName);
private string GetFieldPlaceholder(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
string.IsNullOrWhiteSpace(this.GetValue(fieldName)) ? this.GetFieldDefaultValue(fieldName, fieldDefinition) : string.Empty;
private void UpdateValue(string fieldName, string? value)
{
this.values[fieldName] = value ?? string.Empty;
this.validationMessage = string.Empty;
}
private async Task Save()
{
if (this.toolDefinition is null)
return;
var validationState = await this.ToolSettingsService.ValidateSettingsAsync(this.toolDefinition, this.values, this.implementation);
if (!validationState.IsConfigured)
{
this.validationMessage = !string.IsNullOrWhiteSpace(validationState.Message)
? validationState.Message
: string.Format(T("Please configure the required settings: {0}"), string.Join(", ", validationState.MissingRequiredFields));
return;
}
await this.ToolSettingsService.SaveSettingsAsync(this.toolDefinition, this.values);
this.MudDialog.Close();
}
/// <param name="Key">The group's name from the schema, or empty for the ungrouped fields.</param>
/// <param name="Fields">The fields of this group, in the order the tool declared them.</param>
private sealed record FieldGroup(string Key, List<KeyValuePair<string, ToolSettingsFieldDefinition>> Fields);
}
@@ -0,0 +1,97 @@
@using AIStudio.Tools.ToolCallingSystem
@using AIStudio.Tools.PluginSystem
@inherits SettingsDialogBase
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6" Class="d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.Dataset" Class="mr-2" />
@T("Export tool configuration")
</MudText>
</TitleContent>
<DialogContent>
@if (this.IsAdmin)
{
@if (!string.IsNullOrWhiteSpace(this.message))
{
<MudAlert Severity="@this.messageSeverity" Class="mb-4">@this.message</MudAlert>
}
@if (this.isLoading)
{
<MudProgressCircular Indeterminate="@true" Size="Size.Small" />
<MudText>@T("Loading tool configuration...")</MudText>
}
else if (this.toolDefinition is null || this.implementation is null)
{
@if (string.IsNullOrWhiteSpace(this.message))
{
<MudText>@T("The selected tool could not be loaded.")</MudText>
}
}
else
{
<MudText Typo="Typo.subtitle1" Class="mb-2">@this.implementation.GetDisplayName()</MudText>
<MudText Typo="Typo.body2" Class="mb-4">
@T("Export saved settings as Lua code for your configuration plugin. You can combine exports and adapt the code before deploying it.")
</MudText>
@if (this.areas.Count > 0)
{
<MudPaper Class="pa-3 mb-4 border-dashed border rounded-lg">
<MudText Typo="Typo.subtitle2">@T("Settings to include")</MudText>
@if (this.areas.Count > 1)
{
<MudCheckBox T="bool" Label="@T("Select all")" Value="@this.AllAreasSelected" ValueChanged="@this.SelectAllAreas" Disabled="@this.isExporting" />
}
@foreach (var area in this.areas)
{
<MudCheckBox @key="area.Id" T="bool" Label="@area.Label" Value="@this.selectedAreaIds.Contains(area.Id)" ValueChanged="@(selected => this.SelectArea(area.Id, selected))" Disabled="@this.isExporting" />
}
<MudText Typo="Typo.caption">@T("Each area is independent. Select general settings separately if you want to include them.")</MudText>
</MudPaper>
}
<MudSelect T="ToolSettingsExportMode" Label="@T("Export mode")" @bind-Value="this.mode" Variant="Variant.Outlined" Class="mb-4" Disabled="@this.isExporting" HelperText="@T("This choice applies to settings other than secrets and the minimum provider confidence.")">
<MudSelectItem T="ToolSettingsExportMode" Value="@ToolSettingsExportMode.LOCKED">@T("Locked settings")</MudSelectItem>
<MudSelectItem T="ToolSettingsExportMode" Value="@ToolSettingsExportMode.DEFAULT">@T("Editable defaults")</MudSelectItem>
</MudSelect>
@if (this.WarnAboutEmptyLockedSettings)
{
<MudAlert Severity="Severity.Warning" Class="mb-4">
@string.Format(T("{0} of the selected settings are empty and are exported as empty locked values. Users cannot change a locked setting, so an empty required one leaves the tool unusable. Deselect the areas you have not configured, or export them as editable defaults."), this.EmptySelectedFieldCount)
</MudAlert>
}
<MudPaper Class="pa-3 mb-4 border-dashed border rounded-lg">
<MudCheckBox T="bool" Label="@T("Include encrypted API keys and other secrets")" @bind-Value="this.includeSecrets" Disabled="@(this.isExporting || !this.CanIncludeSecrets)" />
@if (PluginFactory.EnterpriseEncryption?.IsAvailable is not true)
{
<MudText Typo="Typo.body2">@T("No enterprise encryption secret is configured. API keys and other secrets cannot be exported.")</MudText>
}
else if (!this.HasSelectedSecrets)
{
<MudText Typo="Typo.body2">@T("The selected areas contain no configured API keys or other secrets.")</MudText>
}
else
{
<MudText Typo="Typo.body2">@T("Secrets are always exported as locked settings. Recipients need the same enterprise encryption secret to use them.")</MudText>
}
</MudPaper>
<MudPaper Class="pa-3 mb-4 border-dashed border rounded-lg">
<MudCheckBox T="bool" Label="@T("Include minimum provider confidence")" @bind-Value="this.includeMinimumProviderConfidence" Disabled="@this.isExporting" />
<MudText Typo="Typo.body2" Class="mb-2">@string.Format(T("Current requirement: {0}"), this.GetMinimumProviderConfidenceName())</MudText>
<MudText Typo="Typo.body2">@T("This setting is always locked and applies to the entire tool. The configuration plugin locks minimum provider confidence levels together for all tools in its confidence table.")</MudText>
</MudPaper>
}
}
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Text">@T("Cancel")</MudButton>
<MudButton OnClick="@this.Export" Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.ContentCopy" Disabled="@(!this.CanExport)">
@T("Export to clipboard")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,204 @@
using AIStudio.Provider;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs.Settings;
public partial class ToolSettingsExportDialog : SettingsDialogBase
{
[Parameter]
public string ToolId { get; set; } = string.Empty;
[Inject]
private ToolRegistry ToolRegistry { get; init; } = null!;
[Inject]
private ToolSettingsService ToolSettingsService { get; init; } = null!;
[Inject]
private ILogger<ToolSettingsExportDialog> Logger { get; init; } = null!;
private ToolDefinition? toolDefinition;
private IToolImplementation? implementation;
private IReadOnlyList<ExportableSettings> areas = [];
private HashSet<string> selectedAreaIds = new(StringComparer.Ordinal);
private HashSet<string> configuredSecretFields = new(StringComparer.Ordinal);
private HashSet<string> emptyFieldNames = new(StringComparer.Ordinal);
private ToolSettingsExportMode mode = ToolSettingsExportMode.LOCKED;
private bool includeSecrets;
private bool includeMinimumProviderConfidence = true;
private bool isLoading = true;
private bool isExporting;
private bool isDisposed;
private string message = string.Empty;
private Severity messageSeverity = Severity.Error;
private bool IsAdmin => this.SettingsManager.ConfigurationData.App.ShowAdminSettings;
private bool AllAreasSelected => this.areas.Count > 0 && this.areas.All(area => this.selectedAreaIds.Contains(area.Id));
private bool HasSelectedSecrets => this.areas.Any(area => this.selectedAreaIds.Contains(area.Id) && area.FieldNames.Any(this.configuredSecretFields.Contains));
private bool CanIncludeSecrets => this.HasSelectedSecrets && PluginFactory.EnterpriseEncryption?.IsAvailable is true;
/// <summary>
/// How many of the selected settings hold no value, counting a field shared by two areas once.
/// </summary>
/// <remarks>
/// Saving a tool's settings writes every field of its schema, empty ones included, so an area
/// the administrator never filled in still exports. Locked, those empty values are what the
/// recipient is left with and cannot change, which is worth saying before the export.
/// </remarks>
private int EmptySelectedFieldCount => this.areas
.Where(area => this.selectedAreaIds.Contains(area.Id))
.SelectMany(area => area.FieldNames)
.Distinct(StringComparer.Ordinal)
.Count(this.emptyFieldNames.Contains);
private bool WarnAboutEmptyLockedSettings => this.mode is ToolSettingsExportMode.LOCKED && this.EmptySelectedFieldCount > 0;
private bool CanExport => this.IsAdmin && !this.isLoading && !this.isExporting && !this.isDisposed &&
this.toolDefinition is not null && this.implementation is not null && (this.selectedAreaIds.Count > 0 || this.includeMinimumProviderConfidence);
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (!this.IsAdmin)
{
this.Close();
return;
}
try
{
this.toolDefinition = this.ToolRegistry.GetDefinition(this.ToolId);
if (this.toolDefinition is null)
return;
this.implementation = this.ToolRegistry.GetImplementation(this.toolDefinition.ImplementationKey);
if (this.implementation is null)
return;
this.areas = this.implementation.GetExportableSettings(this.toolDefinition);
this.selectedAreaIds = this.areas.Select(area => area.Id).ToHashSet(StringComparer.Ordinal);
// Retain only field names, never the values themselves, so no plaintext secret lives
// in this component. ExportAsync reads effective settings again when the
// administrator exports.
var values = await this.ToolSettingsService.GetSettingsAsync(this.toolDefinition);
this.configuredSecretFields = this.toolDefinition.SettingsSchema.Properties
.Where(property => property.Value.Secret && values.TryGetValue(property.Key, out var value) && !string.IsNullOrWhiteSpace(value))
.Select(property => property.Key)
.ToHashSet(StringComparer.Ordinal);
// A field the export writes as an empty value: it has to be present, because a
// missing one is skipped rather than exported, and it has to be a non-secret,
// because an empty secret is skipped as well.
this.emptyFieldNames = this.toolDefinition.SettingsSchema.Properties
.Where(property => !property.Value.Secret && values.TryGetValue(property.Key, out var value) && string.IsNullOrWhiteSpace(value))
.Select(property => property.Key)
.ToHashSet(StringComparer.Ordinal);
}
catch (Exception e)
{
// A runtime error may contain secret data, so it goes to the log for diagnosis but
// never into the dialog:
this.Logger.LogError(e, "Failed to load the configuration of the tool '{ToolId}' for export.", this.ToolId);
this.toolDefinition = null;
this.message = T("The tool configuration could not be loaded. Please close this dialog and try again.");
}
finally
{
this.isLoading = false;
}
}
private void SelectArea(string areaId, bool selected)
{
if (selected)
this.selectedAreaIds.Add(areaId);
else
this.selectedAreaIds.Remove(areaId);
this.SelectionChanged();
}
private void SelectAllAreas(bool selected)
{
this.selectedAreaIds = selected ? this.areas.Select(area => area.Id).ToHashSet(StringComparer.Ordinal) : new(StringComparer.Ordinal);
this.SelectionChanged();
}
private void SelectionChanged()
{
// A new selection must not keep an invisible opt-in to secrets it no longer contains.
if (!this.CanIncludeSecrets)
this.includeSecrets = false;
this.message = string.Empty;
}
private string GetMinimumProviderConfidenceName()
{
var confidence = this.toolDefinition is null ? ConfidenceLevel.NONE : this.ToolRegistry.GetMinimumProviderConfidence(this.toolDefinition);
return confidence is ConfidenceLevel.NONE ? T("No minimum confidence level chosen") : confidence.GetName();
}
private async Task Export()
{
if (!this.CanExport || this.toolDefinition is null || this.implementation is null)
return;
this.isExporting = true;
this.message = string.Empty;
this.messageSeverity = Severity.Error;
try
{
var options = new ToolSettingsExportOptions
{
SelectedAreaIds = new HashSet<string>(this.selectedAreaIds, StringComparer.Ordinal),
Mode = this.mode,
IncludeSecrets = this.includeSecrets,
IncludeMinimumProviderConfidence = this.includeMinimumProviderConfidence,
};
var result = await this.ToolSettingsService.ExportAsync(this.toolDefinition, this.implementation, options);
if (this.isDisposed || !this.IsAdmin)
return;
if (!result.Success)
{
this.message = result.ErrorMessage;
return;
}
if (string.IsNullOrWhiteSpace(result.LuaCode))
{
this.messageSeverity = Severity.Info;
this.message = T("The selected areas contain no settings to export.");
return;
}
// The runtime reports clipboard success or failure. Keep the dialog open so that
// administrators can retry or export another selection from the same tool.
await this.RustService.CopyText2Clipboard(result.LuaCode);
}
catch (Exception e)
{
this.Logger.LogError(e, "Failed to export the configuration of the tool '{ToolId}'.", this.ToolId);
this.message = T("The tool configuration could not be exported. Please try again.");
}
finally
{
this.isExporting = false;
}
}
protected override void DisposeResources()
{
this.isDisposed = true;
base.DisposeResources();
}
}