mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-17 06:57:59 +00:00
Added mailboxes as local data sources (#1022)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
1 parent
93be539e50
commit
c4400c0ff6
343 files changed
+26051
-3122
No files matched your search
@@ -3,22 +3,22 @@
|
||||
<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"))
|
||||
<MudIcon Icon="@this.item?.Icon" Class="mr-2" />
|
||||
@(this.item?.DisplayName ?? T("Tool Settings"))
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
@if (this.toolDefinition is null)
|
||||
@if (this.item 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()
|
||||
@this.item.Description
|
||||
</MudJustifiedText>
|
||||
|
||||
@if (!this.SettingsManager.IsToolActive(this.toolDefinition.Id))
|
||||
@if (!this.ToolRegistry.IsToolActive(this.item.Id))
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Class="mb-4">@T("This tool has been disabled by your organization.")</MudAlert>
|
||||
}
|
||||
@@ -33,47 +33,59 @@
|
||||
<MudAlert Severity="Severity.Warning" Class="mb-4">@warning</MudAlert>
|
||||
}
|
||||
|
||||
@foreach (var group in this.BuildVisibleFieldGroups())
|
||||
@foreach (var toolSection in this.sections)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-4 border-dashed border rounded-lg">
|
||||
@if (this.ShowsGroupHeader(group))
|
||||
var visibleGroups = BuildVisibleFieldGroups(toolSection);
|
||||
@if (this.ShowsToolHeaders && visibleGroups.Count > 0)
|
||||
{
|
||||
<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>
|
||||
}
|
||||
<MudText Typo="Typo.subtitle1" Class="d-flex align-center mb-2">
|
||||
<MudIcon Icon="@toolSection.Implementation.Icon" Class="mr-2" />
|
||||
@toolSection.Implementation.GetDisplayName()
|
||||
</MudText>
|
||||
}
|
||||
|
||||
@foreach (var group in visibleGroups)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-4 border-dashed border rounded-lg">
|
||||
@if (ShowsGroupHeader(toolSection, group))
|
||||
{
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap" Class="mb-3">
|
||||
<MudText Typo="Typo.subtitle2">@this.GetGroupLabel(toolSection, group.Key)</MudText>
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
|
||||
@foreach (var link in GetGroupLinks(toolSection, group.Key))
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@link.Icon" Href="@link.Url" Target="_blank">
|
||||
@link.Label
|
||||
</MudButton>
|
||||
}
|
||||
</MudStack>
|
||||
</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 configuration-help-justified" 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
|
||||
@foreach (var property in group.Fields)
|
||||
{
|
||||
<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 configuration-help-justified" HelperText="@this.GetFieldDescription(fieldName, field)" Placeholder="@this.GetFieldPlaceholder(fieldName, field)" InputType="@(field.Secret ? InputType.Password : InputType.Text)" Disabled="@this.IsFieldDisabled(fieldName)" />
|
||||
var fieldName = property.Key;
|
||||
var field = property.Value;
|
||||
var fieldOptions = field.GetOptions();
|
||||
if (fieldOptions.Count > 0)
|
||||
{
|
||||
<MudSelect T="string" Label="@GetFieldLabel(toolSection, fieldName, field)" Value="@GetValue(toolSection, fieldName)" ValueChanged="@(value => this.UpdateValue(toolSection, fieldName, value))" Variant="Variant.Outlined" Margin="Margin.Dense" HelperText="@this.GetFieldDescription(toolSection, fieldName, field)" Placeholder="@GetFieldPlaceholder(toolSection, fieldName, field)" Class="mb-3 configuration-help-justified" Disabled="@this.IsFieldDisabled(toolSection, fieldName)">
|
||||
@if (!toolSection.Definition.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="@GetFieldLabel(toolSection, fieldName, field)" Value="@GetValue(toolSection, fieldName)" ValueChanged="@(value => this.UpdateValue(toolSection, fieldName, value))" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 configuration-help-justified" HelperText="@this.GetFieldDescription(toolSection, fieldName, field)" Placeholder="@GetFieldPlaceholder(toolSection, fieldName, field)" InputType="@(field.Secret ? InputType.Password : InputType.Text)" Disabled="@this.IsFieldDisabled(toolSection, fieldName)" />
|
||||
}
|
||||
}
|
||||
</MudPaper>
|
||||
}
|
||||
</MudPaper>
|
||||
}
|
||||
}
|
||||
</DialogContent>
|
||||
@@ -81,8 +93,8 @@
|
||||
<MudButton OnClick="@this.Close" Variant="Variant.Text">
|
||||
@T("Cancel")
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.Save" Variant="Variant.Filled" Disabled="@(this.toolDefinition is null)">
|
||||
<MudButton OnClick="@this.Save" Variant="Variant.Filled" Disabled="@(this.item is null)">
|
||||
@T("Save")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
@@ -4,10 +4,22 @@ using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Dialogs.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Shows and saves the settings of one entry of the tool catalog.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An entry is a tool collection, see ToolCollectionDefinition. Its tools keep their settings to
|
||||
/// themselves, so the dialog shows one section per tool, and an organization still addresses each
|
||||
/// field as "toolId.fieldName". A tool which belongs to no declared collection forms an entry of its
|
||||
/// own, and the dialog looks as it always did for it: one section without a heading.
|
||||
/// </remarks>
|
||||
public partial class ToolSettingsDialog : SettingsDialogBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the collection whose settings to show. The ID of one of its tools stands for the whole collection.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ToolId { get; set; } = string.Empty;
|
||||
public string CollectionId { get; set; } = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private ToolRegistry ToolRegistry { get; init; } = null!;
|
||||
@@ -15,25 +27,34 @@ public partial class ToolSettingsDialog : SettingsDialogBase
|
||||
[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 ToolCatalogItem? item;
|
||||
private IReadOnlyList<ToolSection> sections = [];
|
||||
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);
|
||||
}
|
||||
this.item = await this.ToolRegistry.GetCatalogItemAsync(this.CollectionId);
|
||||
if (this.item is null)
|
||||
return;
|
||||
|
||||
var loadedSections = new List<ToolSection>(this.item.Tools.Count);
|
||||
foreach (var tool in this.item.Tools)
|
||||
loadedSections.Add(new(tool.Definition, tool.Implementation, await this.ToolSettingsService.GetSettingsAsync(tool.Definition), BuildFieldGroups(tool.Definition)));
|
||||
|
||||
this.sections = loadedSections;
|
||||
}
|
||||
|
||||
private string GetValue(string fieldName) => this.values.GetValueOrDefault(fieldName, string.Empty);
|
||||
/// <summary>
|
||||
/// Whether each section is headed by the name of its tool.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only when the entry holds more than one tool. Otherwise the title of the dialog names the
|
||||
/// tool already.
|
||||
/// </remarks>
|
||||
private bool ShowsToolHeaders => this.sections.Count > 1;
|
||||
|
||||
private static string GetValue(ToolSection section, string fieldName) => section.Values.GetValueOrDefault(fieldName, string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Splits the tool's settings fields into the groups the tool declared for them.
|
||||
@@ -66,7 +87,7 @@ public partial class ToolSettingsDialog : SettingsDialogBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The groups as they are rendered right now, without the fields the tool is hiding.
|
||||
/// The groups of one tool 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
|
||||
@@ -76,15 +97,12 @@ public partial class ToolSettingsDialog : SettingsDialogBase
|
||||
/// 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()
|
||||
private static IReadOnlyList<FieldGroup> BuildVisibleFieldGroups(ToolSection section)
|
||||
{
|
||||
if (this.implementation is null)
|
||||
return this.fieldGroups;
|
||||
|
||||
var visibleGroups = new List<FieldGroup>();
|
||||
foreach (var group in this.fieldGroups)
|
||||
foreach (var group in section.FieldGroups)
|
||||
{
|
||||
var visibleFields = group.Fields.Where(field => this.implementation.IsSettingsFieldVisible(field.Key, this.values)).ToList();
|
||||
var visibleFields = group.Fields.Where(field => section.Implementation.IsSettingsFieldVisible(field.Key, section.Values)).ToList();
|
||||
if (visibleFields.Count > 0)
|
||||
visibleGroups.Add(new FieldGroup(group.Key, visibleFields));
|
||||
}
|
||||
@@ -100,84 +118,98 @@ public partial class ToolSettingsDialog : SettingsDialogBase
|
||||
/// 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.
|
||||
/// It counts the boxes of the tool 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);
|
||||
private static bool ShowsGroupHeader(ToolSection section, FieldGroup group) => BuildVisibleFieldGroups(section).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)
|
||||
private string GetGroupLabel(ToolSection section, string groupKey)
|
||||
{
|
||||
var label = this.implementation?.GetSettingsGroupLabel(groupKey) ?? groupKey;
|
||||
var label = section.Implementation.GetSettingsGroupLabel(groupKey);
|
||||
return string.IsNullOrEmpty(label) ? T("General") : label;
|
||||
}
|
||||
|
||||
private IReadOnlyList<ToolSettingsGroupLink> GetGroupLinks(string groupKey) => this.implementation?.GetSettingsGroupLinks(groupKey) ?? [];
|
||||
private static IReadOnlyList<ToolSettingsGroupLink> GetGroupLinks(ToolSection section, string groupKey) => section.Implementation.GetSettingsGroupLinks(groupKey);
|
||||
|
||||
/// <summary>
|
||||
/// What the tool wants to say about the settings as they stand right now.
|
||||
/// What the tools want to say about their 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 IEnumerable<string> GetSettingsWarnings() => this.sections.SelectMany(section => section.Implementation.GetSettingsWarnings(section.Values));
|
||||
|
||||
private string GetFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
|
||||
this.implementation?.GetSettingsFieldLabel(fieldName, fieldDefinition) ?? fieldDefinition.Title;
|
||||
private static string GetFieldLabel(ToolSection section, string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
|
||||
section.Implementation.GetSettingsFieldLabel(fieldName, fieldDefinition);
|
||||
|
||||
private string GetFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
|
||||
this.GetFieldDescriptionWithDefault(fieldName, fieldDefinition);
|
||||
private static string GetFieldDefaultValue(ToolSection section, string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
|
||||
section.Implementation.GetSettingsFieldDefaultValue(fieldName, fieldDefinition) ?? string.Empty;
|
||||
|
||||
private string GetFieldDefaultValue(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
|
||||
this.implementation?.GetSettingsFieldDefaultValue(fieldName, fieldDefinition) ?? string.Empty;
|
||||
|
||||
private string GetFieldDescriptionWithDefault(string fieldName, ToolSettingsFieldDefinition fieldDefinition)
|
||||
private string GetFieldDescription(ToolSection section, string fieldName, ToolSettingsFieldDefinition fieldDefinition)
|
||||
{
|
||||
var description = this.implementation?.GetSettingsFieldDescription(fieldName, fieldDefinition) ?? fieldDefinition.Description;
|
||||
var defaultValue = this.GetFieldDefaultValue(fieldName, fieldDefinition);
|
||||
var description = section.Implementation.GetSettingsFieldDescription(fieldName, fieldDefinition);
|
||||
var defaultValue = GetFieldDefaultValue(section, 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 bool IsFieldDisabled(ToolSection section, string fieldName) => this.ToolSettingsService.IsFieldLocked(section.Definition, fieldName);
|
||||
|
||||
private string GetFieldPlaceholder(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
|
||||
string.IsNullOrWhiteSpace(this.GetValue(fieldName)) ? this.GetFieldDefaultValue(fieldName, fieldDefinition) : string.Empty;
|
||||
private static string GetFieldPlaceholder(ToolSection section, string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
|
||||
string.IsNullOrWhiteSpace(GetValue(section, fieldName)) ? GetFieldDefaultValue(section, fieldName, fieldDefinition) : string.Empty;
|
||||
|
||||
private void UpdateValue(string fieldName, string? value)
|
||||
private void UpdateValue(ToolSection section, string fieldName, string? value)
|
||||
{
|
||||
this.values[fieldName] = value ?? string.Empty;
|
||||
section.Values[fieldName] = value ?? string.Empty;
|
||||
this.validationMessage = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the settings of every tool, and saves those of the tools which have any.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Nothing is saved while one tool is not satisfied, so a collection never ends up with some
|
||||
/// of its tools configured and the others not, which would leave it unusable all the same.
|
||||
/// </remarks>
|
||||
private async Task Save()
|
||||
{
|
||||
if (this.toolDefinition is null)
|
||||
if (this.item is null)
|
||||
return;
|
||||
|
||||
var validationState = await this.ToolSettingsService.ValidateSettingsAsync(this.toolDefinition, this.values, this.implementation);
|
||||
if (!validationState.IsConfigured)
|
||||
foreach (var section in this.sections)
|
||||
{
|
||||
var validationState = await this.ToolSettingsService.ValidateSettingsAsync(section.Definition, section.Values, section.Implementation);
|
||||
if (validationState.IsConfigured)
|
||||
continue;
|
||||
|
||||
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);
|
||||
foreach (var section in this.sections.Where(section => section.Definition.SettingsSchema.Properties.Count > 0))
|
||||
await this.ToolSettingsService.SaveSettingsAsync(section.Definition, section.Values);
|
||||
|
||||
this.MudDialog.Close();
|
||||
}
|
||||
|
||||
/// <param name="Definition">The tool.</param>
|
||||
/// <param name="Implementation">The implementation of the tool, which labels and checks its fields.</param>
|
||||
/// <param name="Values">The values as they are being edited.</param>
|
||||
/// <param name="FieldGroups">The groups of its fields, in the order the tool declared them.</param>
|
||||
private sealed record ToolSection(ToolDefinition Definition, IToolImplementation Implementation, Dictionary<string, string> Values, IReadOnlyList<FieldGroup> FieldGroups);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
<MudProgressCircular Indeterminate="@true" Size="Size.Small" />
|
||||
<MudText>@T("Loading tool configuration...")</MudText>
|
||||
}
|
||||
else if (this.toolDefinition is null || this.implementation is null)
|
||||
else if (this.item is null)
|
||||
{
|
||||
@if (string.IsNullOrWhiteSpace(this.message))
|
||||
{
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-2">@this.implementation.GetDisplayName()</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-2">@this.item.DisplayName</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>
|
||||
|
||||
@@ -8,8 +8,11 @@ namespace AIStudio.Dialogs.Settings;
|
||||
|
||||
public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the collection whose settings to export. The ID of one of its tools stands for the whole collection.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ToolId { get; set; } = string.Empty;
|
||||
public string CollectionId { get; set; } = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private ToolRegistry ToolRegistry { get; init; } = null!;
|
||||
@@ -20,12 +23,11 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
[Inject]
|
||||
private ILogger<ToolSettingsExportDialog> Logger { get; init; } = null!;
|
||||
|
||||
private ToolDefinition? toolDefinition;
|
||||
private IToolImplementation? implementation;
|
||||
private IReadOnlyList<ExportableSettings> areas = [];
|
||||
private ToolCatalogItem? item;
|
||||
private IReadOnlyList<ToolSettingsExportArea> areas = [];
|
||||
private HashSet<string> selectedAreaIds = new(StringComparer.Ordinal);
|
||||
private HashSet<string> configuredSecretFields = new(StringComparer.Ordinal);
|
||||
private HashSet<string> emptyFieldNames = new(StringComparer.Ordinal);
|
||||
private readonly HashSet<ToolField> configuredSecretFields = [];
|
||||
private readonly HashSet<ToolField> emptyFields = [];
|
||||
private ToolSettingsExportMode mode = ToolSettingsExportMode.LOCKED;
|
||||
private bool includeSecrets;
|
||||
private bool includeMinimumProviderConfidence = true;
|
||||
@@ -39,7 +41,7 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
|
||||
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 HasSelectedSecrets => this.SelectedFields.Any(this.configuredSecretFields.Contains);
|
||||
|
||||
private bool CanIncludeSecrets => this.HasSelectedSecrets && PluginFactory.EnterpriseEncryption?.IsAvailable is true;
|
||||
|
||||
@@ -51,16 +53,20 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
/// 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
|
||||
private int EmptySelectedFieldCount => this.SelectedFields.Count(this.emptyFields.Contains);
|
||||
|
||||
/// <summary>
|
||||
/// The fields of all selected areas, each once.
|
||||
/// </summary>
|
||||
private IEnumerable<ToolField> SelectedFields => this.areas
|
||||
.Where(area => this.selectedAreaIds.Contains(area.Id))
|
||||
.SelectMany(area => area.FieldNames)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.Count(this.emptyFieldNames.Contains);
|
||||
.SelectMany(area => area.Area.FieldNames.Select(fieldName => new ToolField(area.Tool.Definition.Id, fieldName)))
|
||||
.Distinct();
|
||||
|
||||
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);
|
||||
this.item is not null && (this.selectedAreaIds.Count > 0 || this.includeMinimumProviderConfidence);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -73,40 +79,38 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
|
||||
try
|
||||
{
|
||||
this.toolDefinition = this.ToolRegistry.GetDefinition(this.ToolId);
|
||||
if (this.toolDefinition is null)
|
||||
this.item = await this.ToolRegistry.GetCatalogItemAsync(this.CollectionId);
|
||||
if (this.item 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.areas = ToolSettingsService.GetExportAreas(this.item.Tools);
|
||||
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);
|
||||
foreach (var tool in this.item.Tools)
|
||||
{
|
||||
var values = await this.ToolSettingsService.GetSettingsAsync(tool.Definition);
|
||||
var properties = tool.Definition.SettingsSchema.Properties;
|
||||
this.configuredSecretFields.UnionWith(properties
|
||||
.Where(property => property.Value.Secret && values.TryGetValue(property.Key, out var value) && !string.IsNullOrWhiteSpace(value))
|
||||
.Select(property => new ToolField(tool.Definition.Id, property.Key)));
|
||||
|
||||
// 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);
|
||||
// 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.emptyFields.UnionWith(properties
|
||||
.Where(property => !property.Value.Secret && values.TryGetValue(property.Key, out var value) && string.IsNullOrWhiteSpace(value))
|
||||
.Select(property => new ToolField(tool.Definition.Id, property.Key)));
|
||||
}
|
||||
}
|
||||
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.Logger.LogError(e, "Failed to load the configuration of the tool collection '{CollectionId}' for export.", this.CollectionId);
|
||||
this.item = null;
|
||||
this.message = T("The tool configuration could not be loaded. Please close this dialog and try again.");
|
||||
}
|
||||
finally
|
||||
@@ -142,13 +146,13 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
|
||||
private string GetMinimumProviderConfidenceName()
|
||||
{
|
||||
var confidence = this.toolDefinition is null ? ConfidenceLevel.NONE : this.ToolRegistry.GetMinimumProviderConfidence(this.toolDefinition);
|
||||
var confidence = this.item is null ? ConfidenceLevel.NONE : this.ToolRegistry.GetMinimumProviderConfidence(this.item.Id);
|
||||
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)
|
||||
if (!this.CanExport || this.item is null)
|
||||
return;
|
||||
|
||||
this.isExporting = true;
|
||||
@@ -164,7 +168,7 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
IncludeMinimumProviderConfidence = this.includeMinimumProviderConfidence,
|
||||
};
|
||||
|
||||
var result = await this.ToolSettingsService.ExportAsync(this.toolDefinition, this.implementation, options);
|
||||
var result = await this.ToolSettingsService.ExportAsync(this.item.Tools, options, this.item.Id, this.ToolRegistry.GetMinimumProviderConfidence(this.item.Id));
|
||||
if (this.isDisposed || !this.IsAdmin)
|
||||
return;
|
||||
|
||||
@@ -187,7 +191,7 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError(e, "Failed to export the configuration of the tool '{ToolId}'.", this.ToolId);
|
||||
this.Logger.LogError(e, "Failed to export the configuration of the tool collection '{CollectionId}'.", this.CollectionId);
|
||||
this.message = T("The tool configuration could not be exported. Please try again.");
|
||||
}
|
||||
finally
|
||||
@@ -201,4 +205,9 @@ public partial class ToolSettingsExportDialog : SettingsDialogBase
|
||||
this.isDisposed = true;
|
||||
base.DisposeResources();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One settings field of one tool. A collection may hold two tools whose fields share a name.
|
||||
/// </summary>
|
||||
private readonly record struct ToolField(string ToolId, string FieldName);
|
||||
}
|
||||
Reference in new issue
Block a user