Merge branch 'main' into chunk-data

This commit is contained in:
PaulKoudelka committed 2026-08-04 18:01:25 +02:00
commit 9932b5870b
484 files changed
+45549 -2467

No files matched your search

@@ -0,0 +1,30 @@
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Review the assistant draft before AI Studio generates the Lua plugin. You can edit the Markdown draft if something should be changed.")
</MudJustifiedText>
<MudTabs Elevation="0" Rounded="true" ApplyEffectsToContainer="true" Outlined="true" PanelClass="pa-2" Class="mb-2">
<MudTabPanel Text="@T("Preview")" Icon="@Icons.Material.Filled.Article">
<MudField Variant="Variant.Outlined" AdornmentIcon="@Icons.Material.Filled.Visibility" Adornment="Adornment.Start" Label="@T("Assistant draft")" FullWidth="true" Class="ma-2 pe-4">
<div style="max-height: 58vh; overflow-y: auto;">
<MudMarkdown Value="@this.DraftMarkdown" Props="Markdown.DefaultConfig" Styling="@this.MarkdownStyling" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE"/>
</div>
</MudField>
</MudTabPanel>
<MudTabPanel Text="@T("Edit")" Icon="@Icons.Material.Filled.Edit">
<MudTextField T="string" @bind-Text="@this.DraftMarkdown" AdornmentIcon="@Icons.Material.Filled.EditNote" Adornment="Adornment.Start" Label="@T("Assistant draft Markdown")" Variant="Variant.Outlined" Lines="16" AutoGrow="@true" MaxLines="32" Class="ma-2"/>
</MudTabPanel>
</MudTabs>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Primary">
@T("Use this draft")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,24 @@
using AIStudio.Components;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class AssistantDraftDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public string DraftMarkdown { get; set; } = string.Empty;
private void Cancel() => this.MudDialog.Cancel();
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(this.DraftMarkdown));
private CodeBlockTheme CodeColorPalette => this.SettingsManager.IsDarkMode ? CodeBlockTheme.Dark : CodeBlockTheme.Default;
private MudMarkdownStyling MarkdownStyling => new()
{
CodeBlock = { Theme = this.CodeColorPalette },
};
}
@@ -12,9 +12,18 @@
else
{
<MudStack Spacing="2">
<MudAlert Severity="Severity.Info" Dense="true">
@T("This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.")
</MudAlert>
@if (this.securityState.IsEnterpriseApproved)
{
<MudAlert Severity="Severity.Success" Dense="true">
@T("This assistant plugin is approved by your organization. A manual security audit is not required.")
</MudAlert>
}
else
{
<MudAlert Severity="Severity.Info" Dense="true">
@T("This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.")
</MudAlert>
}
<MudPaper Class="pa-3 border-dashed border rounded-lg">
<MudText Typo="Typo.h6">@this.plugin.Name</MudText>
@@ -298,9 +307,12 @@
<MudButton OnClick="@this.CloseWithoutActivation" Variant="Variant.Filled">
@(this.audit is null ? T("Cancel") : T("Close"))
</MudButton>
<MudButton OnClick="@this.RunAudit" Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!this.CanRunAudit || this.justAudited)">
@T("Start Security Check")
</MudButton>
@if (!this.securityState.IsEnterpriseApproved)
{
<MudButton OnClick="@this.RunAudit" Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!this.CanRunAudit || this.justAudited)">
@T("Start Security Check")
</MudButton>
}
@if (this.CanEnablePlugin)
{
<MudButton OnClick="@this.EnablePlugin" Variant="Variant.Filled" Color="@this.EnableButtonColor">
@@ -37,6 +37,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private IReadOnlyCollection<TreeItemData<ITreeItem>> fileSystemTreeItems = [];
private CultureInfo currentCultureInfo = CultureInfo.InvariantCulture;
private bool isAuditing;
private PluginAssistantSecurityState securityState = new();
private AIStudio.Settings.Provider CurrentProvider => this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
@@ -50,15 +51,15 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private string MinimumLevelLabel => this.MinimumLevel.GetName();
private bool CanRunAudit => this.plugin is not null && this.CurrentProvider != AIStudio.Settings.Provider.NONE && !this.isAuditing;
private bool CanRunAudit => this.plugin is not null && this.CurrentProvider != AIStudio.Settings.Provider.NONE && !this.isAuditing && !this.securityState.IsEnterpriseApproved;
private bool IsAuditBelowMinimum => this.audit is not null && this.audit.Level < this.MinimumLevel;
private bool IsActivationBlockedBySettings => this.audit is null || this.IsAuditBelowMinimum && this.AuditSettings.BlockActivationBelowMinimum;
private bool IsActivationBlockedBySettings => this.AuditSettings.RequireAuditBeforeActivation && (this.audit is null || this.IsAuditBelowMinimum && this.AuditSettings.BlockActivationBelowMinimum);
private bool RequiresActivationConfirmation => this.audit is not null && this.IsAuditBelowMinimum && !this.AuditSettings.BlockActivationBelowMinimum;
private bool RequiresActivationConfirmation => this.audit is not null && this.IsAuditBelowMinimum && !this.IsActivationBlockedBySettings;
private bool CanEnablePlugin => this.audit is not null && !this.isAuditing && !this.IsActivationBlockedBySettings;
private bool CanEnablePlugin => this.plugin is not null && !this.isAuditing && !this.IsActivationBlockedBySettings;
private Color EnableButtonColor => this.RequiresActivationConfirmation ? Color.Warning : Color.Success;
private bool justAudited;
@@ -74,6 +75,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
.FirstOrDefault(x => x.Id == this.PluginId);
if (this.plugin is not null)
{
this.securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.plugin);
this.promptPreview = await this.plugin.BuildAuditPromptPreviewAsync();
this.promptFallbackPreview = this.plugin.BuildAuditPromptFallbackPreview();
this.plugin.CreateAuditComponentSummary();
@@ -96,6 +98,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
try
{
this.audit = await this.AssistantPluginAuditService.RunAuditAsync(this.plugin);
this.securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.plugin);
}
finally
{
@@ -118,7 +121,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private async Task EnablePlugin()
{
if (this.audit is null)
if (this.plugin is null)
return;
if (this.IsActivationBlockedBySettings)
@@ -137,7 +140,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
{
x => x.Message,
string.Format(
T("The assistant plugin \"{0}\" was audited with the level \"{1}\", which is below the required safety level \"{2}\". Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?"),
T("The assistant plugin '{0}' was audited with the level '{1}', which is below the required safety level '{2}'. Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?"),
this.plugin?.Name ?? T("Unknown plugin"),
this.audit?.Level.GetName() ?? T("Unknown"),
this.MinimumLevelLabel)
@@ -0,0 +1,57 @@
@inherits MSGComponentBase
<MudDialog DefaultFocus="DefaultFocus.None">
<DialogContent>
<MudStack Spacing="2">
@if (!string.IsNullOrWhiteSpace(this.issue))
{
<MudAlert Severity="Severity.Error" Dense="true">
@this.issue
</MudAlert>
}
@if (this.isLoading)
{
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
}
else if (this.plugin is not null)
{
<MudText Typo="Typo.h6">@this.plugin.Name</MudText>
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="1">
<MudLink OnClick="@(async () => await this.CopyToClipboard())">
<MudText Typo="Typo.caption" Class="mud-text-secondary">
@this.pluginFile
</MudText>
</MudLink>
<MudTooltip Text="@this.FullscreenLabel">
<MudIconButton Icon="@this.FullscreenIcon"
OnClick="@this.ToggleFullscreenAsync"
Size="Size.Medium"/>
</MudTooltip>
</MudStack>
<CodeEditor @ref="this.codeEditor" Value="@this.luaCode" Language="CodeEditorLanguage.LUA" Class="mt-n3"/>
}
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Disabled="@this.isSaving" Size="Size.Small">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.SaveAsync"
Disabled="@(!this.CanSave)"
Color="Color.Primary"
Variant="Variant.Filled"
StartIcon="@Icons.Material.Filled.Save"
Size="Size.Small">
@if (this.isSaving)
{
@T("Saving...")
}
else
{
@T("Save")
}
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,150 @@
using System.Text;
using AIStudio.Components;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public sealed record AssistantPluginEditorDialogResult(Guid PluginId, string PluginName);
public partial class AssistantPluginEditorDialog : MSGComponentBase
{
[Inject]
protected RustService RustService { get; init; } = null!;
private const string PLUGIN_FILE_NAME = "plugin.lua";
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(AssistantPluginEditorDialog));
private readonly MudBlazor.DialogOptions optionsFullscreen = new()
{
BackdropClick = false,
CloseButton = true,
FullScreen = true,
FullWidth = true,
NoHeader = true,
};
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Inject]
private AssistantPluginInstallService AssistantPluginInstallService { get; init; } = null!;
[Parameter]
public Guid PluginId { get; set; }
[Parameter]
public string PluginLocalPath { get; set; } = string.Empty;
private IAvailablePlugin? plugin;
private CodeEditor? codeEditor;
private string pluginFile = string.Empty;
private string luaCode = string.Empty;
private string issue = string.Empty;
private bool isLoading = true;
private bool isSaving;
private bool isFullscreen;
private bool CanSave => this.plugin is not null && !this.isLoading && !this.isSaving;
private string FullscreenIcon => this.isFullscreen ? Icons.Material.Filled.FullscreenExit : Icons.Material.Filled.Fullscreen;
private string FullscreenLabel => this.isFullscreen ? T("Exit fullscreen") : T("Fullscreen");
private Func<string> Result2Copy => () => string.IsNullOrEmpty(this.pluginFile) ? string.Empty : this.pluginFile;
protected override async Task OnInitializedAsync()
{
try
{
this.plugin = PluginFactory.AvailablePlugins
.OfType<IAvailablePlugin>()
.FirstOrDefault(x => x.Id == this.PluginId && AreSamePath(x.LocalPath, this.PluginLocalPath));
if (this.plugin is null)
{
this.issue = T("The assistant plugin could not be resolved.");
return;
}
if (this.plugin is { IsInternal: true } || this.plugin.Type is not PluginType.ASSISTANT || string.IsNullOrWhiteSpace(this.plugin.LocalPath))
{
this.issue = T("This plugin cannot be edited.");
return;
}
this.pluginFile = Path.Join(this.plugin.LocalPath, PLUGIN_FILE_NAME);
if (!File.Exists(this.pluginFile))
{
this.issue = T("The plugin.lua file could not be found.");
return;
}
this.luaCode = await File.ReadAllTextAsync(this.pluginFile, Encoding.UTF8);
}
catch (Exception e)
{
this.issue = string.Format(T("The assistant plugin could not be loaded: {0}"), e.Message);
}
finally
{
this.isLoading = false;
}
await base.OnInitializedAsync();
}
private async Task SaveAsync()
{
if (!this.CanSave || this.plugin is null || this.codeEditor is null)
return;
this.isSaving = true;
this.issue = string.Empty;
await this.InvokeAsync(this.StateHasChanged);
try
{
var editedLua = await this.codeEditor.GetCodeAsync();
var result = await this.AssistantPluginInstallService.UpdateInstalledAssistantAsync(this.plugin, editedLua, CancellationToken.None);
if (!result.Success)
{
LOGGER.LogError($"Failed to update assistant plugin '{result.PluginName}' ({result.PluginId}) in '{result.PluginDirectory}' with issue '{result.Issue}'.");
this.issue = result.Issue;
return;
}
this.MudDialog.Close(DialogResult.Ok(new AssistantPluginEditorDialogResult(result.PluginId, result.PluginName)));
}
finally
{
this.isSaving = false;
if (!string.IsNullOrWhiteSpace(this.issue))
await this.InvokeAsync(this.StateHasChanged);
}
}
private async Task ToggleFullscreenAsync()
{
this.isFullscreen = !this.isFullscreen;
await this.MudDialog.SetOptionsAsync(this.isFullscreen ? this.optionsFullscreen : DialogOptions.BLOCKING_FULLSCREEN);
}
private void Cancel() => this.MudDialog.Cancel();
private async Task CopyToClipboard() => await this.RustService.CopyText2Clipboard(this.Result2Copy());
private static bool AreSamePath(string left, string right)
{
if (string.IsNullOrWhiteSpace(left) || string.IsNullOrWhiteSpace(right))
return false;
var comparison = OperatingSystem.IsWindows()
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
return string.Equals(
Path.GetFullPath(left).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
Path.GetFullPath(right).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
comparison);
}
}
@@ -0,0 +1,106 @@
@inherits MSGComponentBase
<MudDialog DefaultFocus="DefaultFocus.None">
<DialogContent>
<MudStack Spacing="3">
@if (!string.IsNullOrWhiteSpace(this.issue))
{
<MudAlert Severity="Severity.Error" Dense="true">
@this.issue
</MudAlert>
}
@if (this.isLoading)
{
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
}
else if (this.assistantPlugin is not null)
{
<MudText Typo="Typo.h6">@this.assistantPlugin.AssistantTitle</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@T("Describe what should change after trying the assistant. AI Studio will revise the installed plugin while keeping the same assistant ID.")</MudText>
<MudTextField T="string"
@bind-Text="@this.changeRequest"
Label="@T("Requested changes")"
Placeholder="@T("Add a field for the target audience and make the final answer shorter.")"
Variant="Variant.Outlined"
Lines="5"
AutoGrow="true"
MaxLines="12"
Immediate="true"
Disabled="@(this.isGenerating || this.isApplying)" />
<CascadingValue Value="Components.META_ASSISTANT">
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider" Disabled="@(this.isGenerating || this.isApplying)" />
</CascadingValue>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.AutoFixHigh"
Disabled="@(!this.CanGenerate)"
OnClick="@(async () => await this.GenerateRevisionAsync())">
@if (this.isGenerating)
{
@T("Creating revision...")
}
else
{
@T("Create revision")
}
</MudButton>
@if (this.isGenerating)
{
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
}
@if (this.revisionCheckResult?.Success is true)
{
<MudAlert Severity="Severity.Success" Dense="true" Icon="@Icons.Material.Filled.CheckCircle">
@string.Format(T("The revised assistant '{0}' is valid and ready to update."), string.IsNullOrWhiteSpace(this.revisedPluginName) ? this.revisionCheckResult.PluginName : this.revisedPluginName)
</MudAlert>
}
@if (!string.IsNullOrWhiteSpace(this.revisedLua))
{
<MudExpansionPanels Dense="true" Elevation="0">
<MudExpansionPanel Dense="true" Class="border-solid border rounded pt-n4" Style="border-color: #BDBDBD">
<TitleContent>
<div class="d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.Code" Class="mr-3" Color="Color.Primary" />
<MudText Typo="Typo.button">
@T("Revised Lua plugin")
</MudText>
</div>
</TitleContent>
<ChildContent>
<MudTextField T="string" Text="@this.revisedLua" ReadOnly="true" Variant="Variant.Outlined" Lines="18" Class="mt-2" Style="font-family: monospace" />
</ChildContent>
</MudExpansionPanel>
</MudExpansionPanels>
}
@if (this.isApplying || this.isAuditing)
{
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
<MudText Typo="Typo.body2">
@(this.isAuditing ? T("Running security audit...") : T("Updating assistant..."))
</MudText>
}
}
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Disabled="@(this.isGenerating || this.isApplying || this.isAuditing)" Size="Size.Small">
@T("Cancel")
</MudButton>
<MudButton OnClick="@(async () => await this.ApplyRevisionAsync())"
Disabled="@(!this.CanApply)"
Color="Color.Primary"
Variant="Variant.Filled"
StartIcon="@Icons.Material.Filled.Save"
Size="Size.Small">
@T("Update assistant")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,255 @@
using System.Text;
using AIStudio.Agents.AssistantAudit;
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public sealed record AssistantPluginRevisionDialogResult(Guid PluginId, string PluginName, PluginAssistantAudit? Audit);
public partial class AssistantPluginRevisionDialog : MSGComponentBase
{
private const string PLUGIN_FILE_NAME = "plugin.lua";
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(AssistantPluginRevisionDialog));
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Inject]
private AssistantPluginGenerationService AssistantPluginGenerationService { get; init; } = null!;
[Inject]
private AssistantPluginInstallService AssistantPluginInstallService { get; init; } = null!;
[Inject]
private AssistantPluginAuditService AssistantPluginAuditService { get; init; } = null!;
[Parameter]
public Guid PluginId { get; set; }
[Parameter]
public string PluginLocalPath { get; set; } = string.Empty;
[Parameter]
public string TestContext { get; set; } = string.Empty;
private IAvailablePlugin? availablePlugin;
private PluginAssistants? assistantPlugin;
private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE;
private string pluginFile = string.Empty;
private string currentLua = string.Empty;
private string changeRequest = string.Empty;
private string revisedLua = string.Empty;
private string revisedPluginName = string.Empty;
private string issue = string.Empty;
private AssistantPluginCheckResult? revisionCheckResult;
private bool isLoading = true;
private bool isGenerating;
private bool isApplying;
private bool isAuditing;
private bool CanGenerate => this.assistantPlugin is not null &&
!this.isLoading &&
!this.isGenerating &&
!this.isApplying &&
!string.IsNullOrWhiteSpace(this.changeRequest);
private bool CanApply => this.availablePlugin is not null &&
this.assistantPlugin is not null &&
!this.isGenerating &&
!this.isApplying &&
!this.isAuditing &&
this.revisionCheckResult?.Success is true &&
!string.IsNullOrWhiteSpace(this.revisedLua);
protected override async Task OnInitializedAsync()
{
try
{
this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.META_ASSISTANT);
this.availablePlugin = PluginFactory.AvailablePlugins
.OfType<IAvailablePlugin>()
.FirstOrDefault(x => x.Id == this.PluginId && AreSamePath(x.LocalPath, this.PluginLocalPath));
this.assistantPlugin = PluginFactory.RunningPlugins
.OfType<PluginAssistants>()
.FirstOrDefault(x => x.Id == this.PluginId && AreSamePath(x.PluginPath, this.PluginLocalPath));
if (this.availablePlugin is null || this.assistantPlugin is null)
{
this.issue = T("The assistant plugin could not be resolved.");
return;
}
if (!CanReviseAssistantPlugin(this.availablePlugin, this.assistantPlugin))
{
this.issue = T("Only locally managed assistant plugins can be revised with AI.");
return;
}
this.pluginFile = Path.Join(this.availablePlugin.LocalPath, PLUGIN_FILE_NAME);
if (!File.Exists(this.pluginFile))
{
this.issue = T("The plugin.lua file could not be found.");
return;
}
this.currentLua = await File.ReadAllTextAsync(this.pluginFile, Encoding.UTF8);
}
catch (Exception e)
{
this.issue = string.Format(T("The assistant plugin could not be loaded: {0}"), e.Message);
}
finally
{
this.isLoading = false;
}
await base.OnInitializedAsync();
}
private async Task GenerateRevisionAsync()
{
if (!this.CanGenerate || this.assistantPlugin is null)
return;
this.isGenerating = true;
this.issue = string.Empty;
this.revisedLua = string.Empty;
this.revisionCheckResult = null;
await this.InvokeAsync(this.StateHasChanged);
try
{
var draft = await this.AssistantPluginGenerationService.GenerateRevisionAsync(
this.assistantPlugin,
this.currentLua,
this.changeRequest,
this.providerSettings,
this.TestContext,
CancellationToken.None);
if (!draft.Success)
{
this.issue = draft.Issue;
return;
}
this.revisedLua = draft.Lua;
this.revisedPluginName = draft.PluginName;
if (this.availablePlugin is null)
return;
this.revisionCheckResult = await this.AssistantPluginInstallService.CheckInstalledAssistantUpdateAsync(this.availablePlugin, this.revisedLua, CancellationToken.None);
if (this.revisionCheckResult.Success)
return;
this.issue = this.revisionCheckResult.Issue;
}
finally
{
this.isGenerating = false;
await this.InvokeAsync(this.StateHasChanged);
}
}
private async Task ApplyRevisionAsync()
{
if (!this.CanApply || this.availablePlugin is null)
return;
this.isApplying = true;
this.issue = string.Empty;
await this.InvokeAsync(this.StateHasChanged);
try
{
var result = await this.AssistantPluginInstallService.UpdateInstalledAssistantAsync(this.availablePlugin, this.revisedLua, CancellationToken.None);
if (!result.Success)
{
LOGGER.LogError($"Failed to revise assistant plugin '{result.PluginName}' ({result.PluginId}) in '{result.PluginDirectory}' with issue '{result.Issue}'.");
this.issue = result.Issue;
return;
}
PluginAssistantAudit? audit = null;
if (this.SettingsManager.ConfigurationData.AssistantPluginAudit.AutomaticallyAuditAssistants)
audit = await this.TryRunAuditAsync(result.PluginId);
this.MudDialog.Close(DialogResult.Ok(new AssistantPluginRevisionDialogResult(result.PluginId, result.PluginName, audit)));
}
finally
{
this.isApplying = false;
if (!string.IsNullOrWhiteSpace(this.issue))
await this.InvokeAsync(this.StateHasChanged);
}
}
private async Task<PluginAssistantAudit?> TryRunAuditAsync(Guid pluginId)
{
var updatedPlugin = PluginFactory.RunningPlugins.OfType<PluginAssistants>().FirstOrDefault(x => x.Id == pluginId);
if (updatedPlugin is null)
return null;
this.isAuditing = true;
await this.InvokeAsync(this.StateHasChanged);
try
{
var audit = await this.AssistantPluginAuditService.RunAuditAsync(updatedPlugin);
if (audit.Level is AssistantAuditLevel.UNKNOWN)
return audit;
UpsertAudit(this.SettingsManager.ConfigurationData.AssistantPluginAudits, audit);
await this.SettingsManager.StoreSettings();
return audit;
}
finally
{
this.isAuditing = false;
}
}
private string? ValidatingProvider(AIStudio.Settings.Provider provider)
{
if (provider.UsedLLMProvider == LLMProviders.NONE)
return T("Please select a provider.");
return null;
}
private void Cancel() => this.MudDialog.Cancel();
private static bool CanReviseAssistantPlugin(IAvailablePlugin availablePlugin, PluginAssistants assistantPlugin) =>
availablePlugin is { IsInternal: false, IsManagedByConfigServer: false, Type: PluginType.ASSISTANT } &&
!string.IsNullOrWhiteSpace(availablePlugin.LocalPath) &&
assistantPlugin is { IsInternal: false, IsManagedByConfigServer: false };
private static void UpsertAudit(IList<PluginAssistantAudit> audits, PluginAssistantAudit audit)
{
var existingIndex = audits.ToList().FindIndex(x => x.PluginId == audit.PluginId);
if (existingIndex >= 0)
audits[existingIndex] = audit;
else
audits.Add(audit);
}
private static bool AreSamePath(string left, string right)
{
if (string.IsNullOrWhiteSpace(left) || string.IsNullOrWhiteSpace(right))
return false;
var comparison = OperatingSystem.IsWindows()
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
return string.Equals(
Path.GetFullPath(left).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
Path.GetFullPath(right).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
comparison);
}
}
@@ -1,9 +1,16 @@
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudJustifiedText Typo="Typo.body1">
@this.Message
</MudJustifiedText>
@if (!string.IsNullOrWhiteSpace(this.MarkdownBody))
{
<MudJustifiedMarkdown Value="@this.MarkdownBody" />
}
else
{
<MudJustifiedText Typo="Typo.body1">
@this.Message
</MudJustifiedText>
}
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@@ -15,6 +15,12 @@ public partial class ConfirmDialog : MSGComponentBase
[Parameter]
public string Message { get; set; } = string.Empty;
/// <summary>
/// Optional Markdown content rendered instead of using the message property.
/// </summary>
[Parameter]
public string MarkdownBody { get; set; } = string.Empty;
private void Cancel() => this.MudDialog.Cancel();
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(true));
@@ -48,19 +48,54 @@
@T("This data source already has indexed embeddings. Delete and recreate it to change the folder path or embedding model.")
</MudAlert>
}
<SelectDirectory @bind-Directory="@this.dataPath" Label="@T("Selected base directory for this data source")" DirectoryDialogTitle="@T("Select the base directory")" Validation="@this.dataSourceValidation.ValidatePath" Disabled="@(!this.CanChangeSourceAndEmbedding)" />
@if (this.CanChangeSourceAndEmbedding)
{
<SelectDirectory @bind-Directory="@this.dataPath" Label="@T("Selected base directory for this data source")" DirectoryDialogTitle="@T("Select the base directory")" Validation="@this.dataSourceValidation.ValidatePath" />
}
else
{
<MudTextField
T="string"
Text="@this.dataPath"
Label="@T("Selected base directory for this data source")"
Class="mb-3"
ReadOnly="@true"
Validation="@this.dataSourceValidation.ValidatePath"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Folder"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
/>
}
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.")
</MudJustifiedText>
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId" Disabled="@(!this.CanChangeSourceAndEmbedding)">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">
@embedding.Name
</MudSelectItem>
}
</MudSelect>
@if (this.CanChangeSourceAndEmbedding)
{
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">
@embedding.Name
</MudSelectItem>
}
</MudSelect>
}
else
{
<MudTextField
T="string"
Text="@this.SelectedEmbeddingNameText"
Label="@T("Embedding")"
Class="mb-3"
ReadOnly="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.DataObject"
AdornmentColor="Color.Info"
Variant="Variant.Outlined"
/>
}
@if (!string.IsNullOrWhiteSpace(this.dataEmbeddingId))
{
@@ -117,6 +117,15 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
private IEnumerable<ConfigurationSelectData<ConfidenceLevel>> ComplianceLevels => ConfigurationSelectDataFactory.GetDataSourceComplianceLevelsData();
private string SelectedEmbeddingNameText
{
get
{
var selectedEmbedding = this.AvailableEmbeddings.FirstOrDefault(x => x.Value == this.dataEmbeddingId);
return string.IsNullOrWhiteSpace(selectedEmbedding.Name) ? T("Unknown") : selectedEmbedding.Name;
}
}
private string SelectedEmbeddingTokenizerText => this.SelectedEmbedding is null
? T("No embedding selected")
: string.IsNullOrWhiteSpace(this.SelectedEmbedding.TokenizerPath)
@@ -48,19 +48,54 @@
@T("This data source already has indexed embeddings. Delete and recreate it to change the file path or embedding model.")
</MudAlert>
}
<SelectFile @bind-File="@this.dataFilePath" Label="@T("Selected file path for this data source")" FileDialogTitle="@T("Select the file")" Validation="@this.dataSourceValidation.ValidateFilePath" Disabled="@(!this.CanChangeSourceAndEmbedding)" />
@if (this.CanChangeSourceAndEmbedding)
{
<SelectFile @bind-File="@this.dataFilePath" Label="@T("Selected file path for this data source")" FileDialogTitle="@T("Select the file")" Validation="@this.dataSourceValidation.ValidateFilePath" />
}
else
{
<MudTextField
T="string"
Text="@this.dataFilePath"
Label="@T("Selected file path for this data source")"
Class="mb-3"
ReadOnly="@true"
Validation="@this.dataSourceValidation.ValidateFilePath"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
/>
}
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.")
</MudJustifiedText>
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId" Disabled="@(!this.CanChangeSourceAndEmbedding)">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">
@embedding.Name
</MudSelectItem>
}
</MudSelect>
@if (this.CanChangeSourceAndEmbedding)
{
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">
@embedding.Name
</MudSelectItem>
}
</MudSelect>
}
else
{
<MudTextField
T="string"
Text="@this.SelectedEmbeddingNameText"
Label="@T("Embedding")"
Class="mb-3"
ReadOnly="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.DataObject"
AdornmentColor="Color.Info"
Variant="Variant.Outlined"
/>
}
@if (!string.IsNullOrWhiteSpace(this.dataEmbeddingId))
{
@@ -117,6 +117,15 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
private IEnumerable<ConfigurationSelectData<ConfidenceLevel>> ComplianceLevels => ConfigurationSelectDataFactory.GetDataSourceComplianceLevelsData();
private string SelectedEmbeddingNameText
{
get
{
var selectedEmbedding = this.AvailableEmbeddings.FirstOrDefault(x => x.Value == this.dataEmbeddingId);
return string.IsNullOrWhiteSpace(selectedEmbedding.Name) ? T("Unknown") : selectedEmbedding.Name;
}
}
private string SelectedEmbeddingTokenizerText => this.SelectedEmbedding is null
? T("No embedding selected")
: string.IsNullOrWhiteSpace(this.SelectedEmbedding.TokenizerPath)
@@ -8,7 +8,7 @@
@if (this.Document is null)
{
<ReadFileContent Text="@T("Load file")" @bind-FileContent="@this.FileContent"/>
<ReadFileContent Text="@T("Load file")" @bind-FileContent="@this.FileContent" EnableDragDrop="true" Layer="@DropLayers.DIALOGS" CatchAllDocuments="true"/>
}
else
{
@@ -158,17 +158,26 @@
AdornmentColor="Color.Info"
Validation="@this.ValidateEmbeddingBatchSize"
HelperText="@T("How many chunks are sent to the embedding provider at once. The default is 1.")"/>
<SelectFile
File="@this.dataFilePath"
FileChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
FileDialogTitle="@T("Choose a custom tokenizer here")"
Filter="[FileTypes.JSON]"
IsClearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@(this.dataCustomTokenizerValidationIssue)"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
OnClear="@this.ClearPathTokenizer" />
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
<MudTextField
T="string"
Text="@this.dataFilePath"
TextChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
Clearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@this.dataCustomTokenizerValidationIssue"
OnClearButtonClick="@this.ClearPathTokenizer"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@this.isTokenizerFileDialogOpen" OnClick="@this.OpenTokenizerFileDialog">
@T("Choose File")
</MudButton>
</MudStack>
</MudCollapse>
</MudStack>
}
@@ -1,4 +1,3 @@
using AIStudio.Chat;
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings;
@@ -104,6 +103,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private string dataCustomTokenizerValidationIssue = string.Empty;
private Task dataTokenizerValidationTask = Task.CompletedTask;
private bool dataStoreWasAttempted;
private bool isTokenizerFileDialogOpen;
private bool showExpertSettings;
private int dataTokenizerValidationRevision;
@@ -317,6 +317,24 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
}
}
private async Task OpenTokenizerFileDialog()
{
if (this.isTokenizerFileDialogOpen)
return;
this.isTokenizerFileDialogOpen = true;
try
{
var response = await this.RustService.SelectFile(T("Choose a custom tokenizer here"), [ FileTypes.JSON ], string.IsNullOrWhiteSpace(this.dataFilePath) ? null : this.dataFilePath);
if (!response.UserCancelled)
await this.OnDataFilePathChanged(response.SelectedFilePath);
}
finally
{
this.isTokenizerFileDialogOpen = false;
}
}
private Task ClearPathTokenizer(MouseEventArgs _)
{
return this.OnDataFilePathChanged(string.Empty);
@@ -8,7 +8,15 @@
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
<MudStack Row="@true" AlignItems="AlignItems.Center">
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider">
<MudSelect T="LLMProviders"
Value="@this.DataLLMProvider"
ValueChanged="@this.OnProviderChanged"
Label="@T("Provider")"
Class="mb-3"
OpenIcon="@Icons.Material.Filled.AccountBalance"
AdornmentColor="Color.Info"
Adornment="Adornment.Start"
Validation="@this.providerValidation.ValidatingProvider">
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
{
<MudSelectItem Value="@provider">
@@ -106,7 +114,9 @@
}
else
{
<MudSelect @bind-Value="@this.DataModel"
<MudSelect T="Provider.Model"
Value="@this.DataModel"
ValueChanged="@(async model => await this.OnModelChanged(model))"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info"
Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
@@ -157,17 +167,26 @@
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("For better token estimates, you can configure a custom tokenizer for this provider.")
</MudJustifiedText>
<SelectFile
File="@this.dataFilePath"
FileChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
FileDialogTitle="@T("Choose a custom tokenizer here")"
Filter="[FileTypes.JSON]"
IsClearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@(this.dataCustomTokenizerValidationIssue)"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
OnClear="@this.ClearPathTokenizer" />
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
<MudTextField
T="string"
Text="@this.dataFilePath"
TextChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
Clearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@this.dataCustomTokenizerValidationIssue"
OnClearButtonClick="@this.ClearPathTokenizer"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@this.isTokenizerFileDialogOpen" OnClick="@this.OpenTokenizerFileDialog">
@T("Choose File")
</MudButton>
</MudStack>
}
<MudStack>
@@ -176,8 +195,67 @@
</MudButton>
<MudDivider />
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudJustifiedText Class="mb-5">
@T("Please be aware: This section is for experts only. You are responsible for verifying the correctness of the additional parameters you provide to the API call. By default, AI Studio uses the OpenAI-compatible chat completions API, when that it is supported by the underlying service and model.")
<MudAlert Severity="Severity.Warning" Class="mb-4">
@T("Please be careful: wrong expert settings can break model usage, disable supported features, or make unsupported features appear available.")
</MudAlert>
<MudText Typo="Typo.subtitle2" Class="mb-2">
@T("Override Model Capabilities")
</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@T("Stored default model capabilities may not reflect its full range. Override them here if needed.")
</MudJustifiedText>
<MudStack Class="mb-4" Spacing="2">
@foreach (var capability in SWITCH_CAPABILITY_OVERRIDES)
{
<MudPaper Outlined="@true" Class="pa-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap">
<MudStack Spacing="0">
<MudText Typo="Typo.body2">
@this.GetCapabilityOverrideLabel(capability)
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
@this.GetCapabilityEffectiveLabel(capability)
</MudText>
</MudStack>
<MudStack Row="@true" AlignItems="AlignItems.Center" Spacing="2">
<MudSwitch T="bool"
Value="@this.IsCapabilityEnabled(capability)"
ValueChanged="@(value => this.OnCapabilitySwitchChanged(capability, value))"
Color="Color.Primary" />
<MudButton Variant="Variant.Outlined"
Color="Color.Default"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.RestartAlt"
Disabled="@(!this.HasCapabilityOverride(capability))"
OnClick="@(() => this.ResetCapabilityOverride(capability))">
@T("Reset")
</MudButton>
</MudStack>
</MudStack>
</MudPaper>
}
<MudPaper Outlined="@true" Class="pa-3">
<MudSelect T="ReasoningOverrideMode"
Value="@this.GetReasoningOverrideMode()"
ValueChanged="@this.SetReasoningOverrideMode"
Label="@T("Reasoning (thinking) behavior")"
HelperText="@this.GetReasoningOverrideModeDescription(this.GetReasoningOverrideMode())"
Variant="Variant.Outlined"
Margin="Margin.Dense"
OpenIcon="@Icons.Material.Filled.Psychology"
AdornmentColor="Color.Info"
Adornment="Adornment.Start">
@foreach (var mode in REASONING_OVERRIDE_MODES)
{
<MudSelectItem Value="@mode">
@this.GetReasoningOverrideModeLabel(mode)
</MudSelectItem>
}
</MudSelect>
</MudPaper>
</MudStack>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@string.Format(T("The current model uses the {0}."), this.GetCurrentModelApiLabel())
</MudJustifiedText>
<MudTextField T="string" Label=@T("Additional API parameters") Variant="Variant.Outlined" Lines="4" AutoGrow="true" MaxLines="10" HelperText=@T("""Add the parameters in proper JSON formatting, e.g., "temperature": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.""") Placeholder="@GetPlaceholderExpertSettings" @bind-Value="@this.AdditionalJsonApiParameters" Immediate="true" Validation="@this.ValidateAdditionalJsonApiParameters" OnBlur="@this.OnInputChangeExpertSettings"/>
</MudCollapse>
@@ -5,6 +5,7 @@ using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Provider.HuggingFace;
using AIStudio.Tools.Rust;
using AIStudio.Settings;
using AIStudio.Tools.Services;
using AIStudio.Tools.Validation;
@@ -20,6 +21,15 @@ namespace AIStudio.Dialogs;
/// </summary>
public partial class ProviderDialog : MSGComponentBase, ISecretId
{
private enum ReasoningOverrideMode
{
AUTOMATIC,
NO_REASONING,
CAN_BE_ENABLED,
ON_BY_DEFAULT,
ALWAYS_ON
}
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -88,6 +98,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
[Parameter]
public string DataTokenizerPath { get; set; } = string.Empty;
[Parameter]
public ProviderCapabilityOverrides? DataCapabilityOverrides { get; set; }
[Inject]
private RustService RustService { get; init; } = null!;
@@ -96,7 +109,23 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private ILogger<ProviderDialog> Logger { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private static readonly IReadOnlyList<Capability> SWITCH_CAPABILITY_OVERRIDES =
[
Capability.AUDIO_INPUT,
Capability.MULTIPLE_IMAGE_INPUT,
Capability.SPEECH_INPUT,
Capability.VIDEO_INPUT
];
private static readonly IReadOnlyList<ReasoningOverrideMode> REASONING_OVERRIDE_MODES =
[
ReasoningOverrideMode.AUTOMATIC,
ReasoningOverrideMode.NO_REASONING,
ReasoningOverrideMode.CAN_BE_ENABLED,
ReasoningOverrideMode.ON_BY_DEFAULT,
ReasoningOverrideMode.ALWAYS_ON
];
/// <summary>
/// The list of used instance names. We need this to check for uniqueness.
/// </summary>
@@ -113,9 +142,11 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private string dataCustomTokenizerValidationIssue = string.Empty;
private Task dataTokenizerValidationTask = Task.CompletedTask;
private bool dataStoreWasAttempted;
private bool isTokenizerFileDialogOpen;
private int dataTokenizerValidationRevision;
private bool usesLegacySystemModelFallback;
private bool showExpertSettings;
private ProviderCapabilityOverrides capabilityOverrides = new();
// We get the form reference from Blazor code to validate it manually:
private MudForm form = null!;
@@ -143,28 +174,13 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
{
var cleanedHostname = this.DataHostname.Trim();
// Determine the model based on the provider and host configuration:
Model model;
if (this.IsLLMModelSelectionHidden)
{
// Use system model placeholder for legacy hosts that don't support model selection:
model = Model.SYSTEM_MODEL;
}
else if (this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE)
{
// These providers require manual model entry:
model = new Model(this.dataManuallyModel, null);
}
else
model = this.DataModel;
return new()
{
Num = this.DataNum,
Id = this.DataId,
InstanceName = this.DataInstanceName,
UsedLLMProvider = this.DataLLMProvider,
Model = model,
Model = this.GetSelectedModel(),
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
IsEnterpriseConfiguration = false,
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
@@ -172,9 +188,21 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
HFInferenceProvider = this.HFInferenceProviderId,
AdditionalJsonApiParameters = this.AdditionalJsonApiParameters,
TokenizerPath = this.dataFilePath,
CapabilityOverrides = this.capabilityOverrides.HasOverrides ? this.capabilityOverrides : null,
};
}
private Model GetSelectedModel()
{
if (this.IsLLMModelSelectionHidden)
return Model.SYSTEM_MODEL;
if (this.DataLLMProvider.IsLLMModelProvidedManually())
return new Model(this.dataManuallyModel, null);
return this.DataModel;
}
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
@@ -190,7 +218,8 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
this.UsedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList();
#pragma warning restore MWAIS0001
this.showExpertSettings = !string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters);
this.capabilityOverrides = this.DataCapabilityOverrides ?? new();
this.showExpertSettings = !string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters) || this.capabilityOverrides.HasOverrides;
// When editing, we need to load the data:
if(this.IsEditing)
@@ -318,6 +347,24 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
}
}
private async Task OpenTokenizerFileDialog()
{
if (this.isTokenizerFileDialogOpen)
return;
this.isTokenizerFileDialogOpen = true;
try
{
var response = await this.RustService.SelectFile(T("Choose a custom tokenizer here"), [ FileTypes.JSON ], string.IsNullOrWhiteSpace(this.dataFilePath) ? null : this.dataFilePath);
if (!response.UserCancelled)
await this.OnDataFilePathChanged(response.SelectedFilePath);
}
finally
{
this.isTokenizerFileDialogOpen = false;
}
}
private Task ClearPathTokenizer(MouseEventArgs _)
{
return this.OnDataFilePathChanged(string.Empty);
@@ -384,16 +431,35 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
return this.RustService.StoreTokenizer(tokenizerId, this.dataFilePath);
}
private void OnProviderChanged(LLMProviders selectedProvider)
{
this.DataLLMProvider = selectedProvider;
this.DataModel = default;
this.dataManuallyModel = string.Empty;
this.capabilityOverrides = new();
this.availableModels.Clear();
this.dataLoadingModelsIssue = string.Empty;
this.usesLegacySystemModelFallback = false;
}
private void OnHostChanged(Host selectedHost)
{
// When the host changes, reset the model selection state:
this.DataHost = selectedHost;
this.DataModel = default;
this.dataManuallyModel = string.Empty;
this.capabilityOverrides = new();
this.availableModels.Clear();
this.dataLoadingModelsIssue = string.Empty;
this.usesLegacySystemModelFallback = false;
}
private Task OnModelChanged(Model selectedModel)
{
this.DataModel = selectedModel;
this.capabilityOverrides = new();
return Task.CompletedTask;
}
private async Task ReloadModels()
{
@@ -459,6 +525,156 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private void ToggleExpertSettings() => this.showExpertSettings = !this.showExpertSettings;
private void SetCapabilityOverride(Capability capability, bool value)
{
this.capabilityOverrides = this.capabilityOverrides.SetOverride(capability, value);
}
private Task OnCapabilitySwitchChanged(Capability capability, bool value)
{
this.SetCapabilityOverride(capability, value);
return Task.CompletedTask;
}
private void ResetCapabilityOverride(Capability capability) =>
this.capabilityOverrides = this.capabilityOverrides.SetOverride(capability, null);
private ReasoningOverrideMode GetReasoningOverrideMode()
{
var alwaysReasoning = this.capabilityOverrides.GetOverride(Capability.ALWAYS_REASONING);
var optionalReasoning = this.capabilityOverrides.GetOverride(Capability.OPTIONAL_REASONING);
var reasoningByDefault = this.capabilityOverrides.GetOverride(Capability.REASONING_BY_DEFAULT);
if (alwaysReasoning is null && optionalReasoning is null && reasoningByDefault is null)
return ReasoningOverrideMode.AUTOMATIC;
var capabilities = this.GetCurrentModelCapabilities();
if (capabilities.Contains(Capability.ALWAYS_REASONING))
return ReasoningOverrideMode.ALWAYS_ON;
if (capabilities.Contains(Capability.REASONING_BY_DEFAULT))
return ReasoningOverrideMode.ON_BY_DEFAULT;
if (capabilities.Contains(Capability.OPTIONAL_REASONING))
return ReasoningOverrideMode.CAN_BE_ENABLED;
return ReasoningOverrideMode.NO_REASONING;
}
private ReasoningOverrideMode GetAutomaticReasoningOverrideMode()
{
var capabilities = this.GetAutomaticModelCapabilities();
if (capabilities.Contains(Capability.ALWAYS_REASONING))
return ReasoningOverrideMode.ALWAYS_ON;
if (capabilities.Contains(Capability.REASONING_BY_DEFAULT))
return ReasoningOverrideMode.ON_BY_DEFAULT;
if (capabilities.Contains(Capability.OPTIONAL_REASONING))
return ReasoningOverrideMode.CAN_BE_ENABLED;
return ReasoningOverrideMode.NO_REASONING;
}
private void SetReasoningOverrideMode(ReasoningOverrideMode mode)
{
this.capabilityOverrides = mode switch
{
ReasoningOverrideMode.AUTOMATIC => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, null)
.SetOverride(Capability.OPTIONAL_REASONING, null)
.SetOverride(Capability.REASONING_BY_DEFAULT, null),
ReasoningOverrideMode.NO_REASONING => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, false)
.SetOverride(Capability.OPTIONAL_REASONING, false)
.SetOverride(Capability.REASONING_BY_DEFAULT, false),
ReasoningOverrideMode.CAN_BE_ENABLED => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, false)
.SetOverride(Capability.OPTIONAL_REASONING, true)
.SetOverride(Capability.REASONING_BY_DEFAULT, false),
ReasoningOverrideMode.ON_BY_DEFAULT => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, false)
.SetOverride(Capability.OPTIONAL_REASONING, true)
.SetOverride(Capability.REASONING_BY_DEFAULT, true),
ReasoningOverrideMode.ALWAYS_ON => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, true)
.SetOverride(Capability.OPTIONAL_REASONING, false)
.SetOverride(Capability.REASONING_BY_DEFAULT, false),
_ => this.capabilityOverrides
};
}
private string GetReasoningOverrideModeLabel(ReasoningOverrideMode mode) => mode switch
{
ReasoningOverrideMode.AUTOMATIC => T("Automatic"),
ReasoningOverrideMode.NO_REASONING => T("No reasoning (thinking)"),
ReasoningOverrideMode.CAN_BE_ENABLED => T("Can be enabled"),
ReasoningOverrideMode.ON_BY_DEFAULT => T("On by default"),
ReasoningOverrideMode.ALWAYS_ON => T("Always on"),
_ => mode.ToString()
};
private string GetReasoningOverrideModeDescription(ReasoningOverrideMode mode) => mode switch
{
ReasoningOverrideMode.AUTOMATIC => string.Format(T("Use detected model behavior: {0}."), this.GetReasoningOverrideModeLabel(this.GetAutomaticReasoningOverrideMode())),
ReasoningOverrideMode.NO_REASONING => T("No reasoning (thinking) capability."),
ReasoningOverrideMode.CAN_BE_ENABLED => T("Reasoning (thinking) is available, but off unless additional API parameters enable it."),
ReasoningOverrideMode.ON_BY_DEFAULT => T("Reasoning (thinking) is available and on unless additional API parameters disable it."),
ReasoningOverrideMode.ALWAYS_ON => T("The model always uses reasoning (thinking); it cannot be disabled."),
_ => string.Empty
};
private bool HasCapabilityOverride(Capability capability) => this.capabilityOverrides.GetOverride(capability) is not null;
private bool IsCapabilityEnabled(Capability capability)
{
var capabilities = this.GetCurrentModelCapabilities();
return capabilities.Contains(capability);
}
private string GetCapabilityEffectiveLabel(Capability capability)
{
var isEnabled = this.IsCapabilityEnabled(capability);
if (this.HasCapabilityOverride(capability))
return isEnabled ? T("Enabled") : T("Disabled");
return isEnabled ? T("Enabled (Auto)") : T("Disabled (Auto)");
}
private List<Capability> GetCurrentModelCapabilities()
{
var currentProviderSettings = this.CreateProviderSettings();
return currentProviderSettings.GetModelCapabilities();
}
private List<Capability> GetAutomaticModelCapabilities() => this.DataLLMProvider.GetModelCapabilities(this.GetSelectedModel());
private string GetCurrentModelApiLabel()
{
var capabilities = this.GetCurrentModelCapabilities();
if (capabilities.Contains(Capability.RESPONSES_API))
return "Responses API";
if (capabilities.Contains(Capability.CHAT_COMPLETION_API))
return "Chat Completions API";
return "Unknown";
}
private string GetCapabilityOverrideLabel(Capability capability) => capability switch
{
Capability.AUDIO_INPUT => T("Audio input"),
Capability.MULTIPLE_IMAGE_INPUT => T("Multiple image input"),
Capability.SPEECH_INPUT => T("Speech input"),
Capability.VIDEO_INPUT => T("Video input"),
Capability.ALWAYS_REASONING => T("Always reasoning"),
_ => capability.ToString()
};
private void OnInputChangeExpertSettings()
{
this.AdditionalJsonApiParameters = NormalizeAdditionalJsonApiParameters(this.AdditionalJsonApiParameters)
@@ -624,10 +840,10 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
return true;
}
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
private static string GetPlaceholderExpertSettings =>
private static string GetPlaceholderExpertSettings =>
"""
"temperature": 0.5,
"top_p": 0.9,
@@ -19,9 +19,6 @@ public abstract class SettingsDialogBase : MSGComponentBase
[Inject]
protected RustService RustService { get; init; } = null!;
[Inject]
protected ISnackbar Snackbar { get; init; } = null!;
protected readonly List<ConfigurationSelectData<string>> AvailableLLMProviders = new();
protected readonly List<ConfigurationSelectData<string>> AvailableEmbeddingProviders = new();
@@ -25,7 +25,7 @@
@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.")"/>
<ConfigurationSelect OptionDescription="@T("Apply default data source option when sending assistant results to chat")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior)" Data="@ConfigurationSelectDataFactory.GetSendToChatDataSourceBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior = selectedValue)" OptionHelp="@T("Do you want to apply the default data source options when sending assistant results to chat?")"/>
<ConfigurationSelect OptionDescription="@T("Apply default data source option when sending assistant results to chat")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior)" Data="@ConfigurationSelectDataFactory.GetSendToChatDataSourceBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior = selectedValue)" OptionHelp="@T("Do you want to apply the default data source options when sending assistant results to chat?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.Chat, x => x.SendToChatDataSourceBehavior, out var meta) && meta.IsLocked"/>
}
</DialogContent>
<DialogActions>
@@ -63,7 +63,7 @@
<MudMenuItem Icon="@Icons.Material.Filled.Link" OnClick="@(() => this.ExportChatTemplateWithSharedAttachmentPaths(context))">
@T("Use shared attachment paths")
</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Folder" OnClick="@(() => this.ExportChatTemplateWithPackagedAttachments(context))">
<MudMenuItem Icon="@Icons.Material.Filled.Folder" Disabled="@this.isPluginDirectoryDialogOpen" OnClick="@(() => this.ExportChatTemplateWithPackagedAttachments(context))">
@T("Copy attachments into plugin")
</MudMenuItem>
</MudMenu>
@@ -6,6 +6,8 @@ namespace AIStudio.Dialogs.Settings;
public partial class SettingsDialogChatTemplate : SettingsDialogBase
{
private bool isPluginDirectoryDialogOpen;
[Parameter]
public bool CreateTemplateFromExistingChatThread { get; set; }
@@ -131,7 +133,7 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
private async Task ExportChatTemplateWithPackagedAttachments(ChatTemplate chatTemplate)
{
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings || this.isPluginDirectoryDialogOpen)
return;
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
@@ -143,11 +145,19 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
return;
}
var pluginDirectoryResponse = await this.RustService.SelectDirectory(T("Select configuration plugin folder"));
if (pluginDirectoryResponse.UserCancelled)
return;
this.isPluginDirectoryDialogOpen = true;
try
{
var pluginDirectoryResponse = await this.RustService.SelectDirectory(T("Select configuration plugin folder"));
if (pluginDirectoryResponse.UserCancelled)
return;
await this.CopyPackagedChatTemplateLuaToClipboard(chatTemplate, pluginDirectoryResponse.SelectedDirectory);
await this.CopyPackagedChatTemplateLuaToClipboard(chatTemplate, pluginDirectoryResponse.SelectedDirectory);
}
finally
{
this.isPluginDirectoryDialogOpen = false;
}
}
private async Task CopyChatTemplateLuaToClipboard(ChatTemplate chatTemplate)
@@ -162,7 +172,7 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
}
if (!string.IsNullOrWhiteSpace(luaCode))
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
await this.RustService.CopyText2Clipboard(luaCode);
}
private async Task CopyPackagedChatTemplateLuaToClipboard(ChatTemplate chatTemplate, string pluginDirectory)
@@ -177,6 +187,6 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
}
if (!string.IsNullOrWhiteSpace(luaCode))
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
await this.RustService.CopyText2Clipboard(luaCode);
}
}
@@ -1,4 +1,3 @@
@using AIStudio.Assistants.Coding
@using AIStudio.Settings
@inherits SettingsDialogBase
@@ -11,13 +10,8 @@
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="@T("Preselect coding options?")" LabelOn="@T("Coding options are preselected")" LabelOff="@T("No coding options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Coding.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect the coding options. This is might be useful when you prefer a specific programming language or LLM model.")"/>
<ConfigurationOption OptionDescription="@T("Preselect coding options?")" LabelOn="@T("Coding options are preselected")" LabelOff="@T("No coding options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Coding.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect coding assistant options such as compiler message input, provider, and profile.")"/>
<ConfigurationOption OptionDescription="@T("Preselect compiler messages?")" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" LabelOn="@T("Compiler messages are preselected")" LabelOff="@T("Compiler messages are not preselected")" State="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectCompilerMessages)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Coding.PreselectCompilerMessages = updatedState)" />
<ConfigurationSelect OptionDescription="@T("Preselect a programming language")" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProgrammingLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonCodingLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProgrammingLanguage = selectedValue)" OptionHelp="@T("Which programming language should be preselected for added contexts?")"/>
@if (this.SettingsManager.ConfigurationData.Coding.PreselectedProgrammingLanguage is CommonCodingLanguages.OTHER)
{
<ConfigurationText OptionDescription="@T("Preselect another programming language")" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" Icon="@Icons.Material.Filled.Code" Text="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage = updatedText)"/>
}
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.MinimumProviderConfidence = selectedValue)"/>
<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.")"/>
@@ -149,7 +149,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
{
var publicLuaCode = eriDataSource.ExportAsConfigurationSection();
if (!string.IsNullOrWhiteSpace(publicLuaCode))
await this.RustService.CopyText2Clipboard(this.Snackbar, publicLuaCode);
await this.RustService.CopyText2Clipboard(publicLuaCode);
return;
}
@@ -218,7 +218,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
if (string.IsNullOrWhiteSpace(luaCode))
return;
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
await this.RustService.CopyText2Clipboard(luaCode);
}
private async Task EditDataSource(IDataSource dataSource)
@@ -75,7 +75,7 @@ public partial class SettingsDialogProfiles : SettingsDialogBase
var luaCode = profile.ExportAsConfigurationSection();
if (!string.IsNullOrWhiteSpace(luaCode))
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
await this.RustService.CopyText2Clipboard(luaCode);
}
private async Task DeleteProfile(Profile profile)
@@ -0,0 +1,32 @@
@using AIStudio.Settings
@inherits SettingsDialogBase
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6" Class="d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.DashboardCustomize" Class="mr-2"/>
@T("Assistant: Visual Briefing defaults")
</MudText>
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationSelect OptionDescription="@T("Default target language")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedTargetLanguage = value)"/>
@if (this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedTargetLanguage is CommonLanguages.OTHER)
{
<ConfigurationText OptionDescription="@T("Default custom target language")" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedOtherLanguage)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedOtherLanguage = value)"/>
}
<ConfigurationSelect OptionDescription="@T("Default audience profile")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceProfile)" Data="@ConfigurationSelectDataFactory.GetSlideBuilderAudienceProfileData()" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceProfile = value)"/>
<ConfigurationSelect OptionDescription="@T("Default audience age group")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceAgeGroup)" Data="@ConfigurationSelectDataFactory.GetSlideBuilderAudienceAgeGroupData()" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceAgeGroup = value)"/>
<ConfigurationSelect OptionDescription="@T("Default audience organizational level")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceOrganizationalLevel)" Data="@ConfigurationSelectDataFactory.GetSlideBuilderAudienceOrganizationalLevelData()" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceOrganizationalLevel = value)"/>
<ConfigurationSelect OptionDescription="@T("Default audience expertise")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceExpertise)" Data="@ConfigurationSelectDataFactory.GetSlideBuilderAudienceExpertiseData()" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedAudienceExpertise = value)"/>
<ConfigurationOption OptionDescription="@T("Show source references by default?")" LabelOn="@T("Source references are visible")" LabelOff="@T("Source references are hidden")" State="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.ShowSourceReferences)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.ShowSourceReferences = value)"/>
<ConfigurationOption OptionDescription="@T("Optimize large visual assets by default?")" LabelOn="@T("Large visual assets are optimized")" LabelOff="@T("Visual assets keep their original size")" State="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.OptimizeImages)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.OptimizeImages = value)"/>
<ConfigurationMinConfidenceSelection RestrictToGlobalMinimumConfidence="true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.MinimumProviderConfidence)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.MinimumProviderConfidence = value)"/>
<ConfigurationProviderSelection Component="Components.VISUAL_BRIEFING_ASSISTANT" Data="@this.AvailableLLMProviders" SelectedValue="@(() => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedProvider)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedProvider = value)"/>
<ConfigurationSelect OptionDescription="@T("Default profile")" SelectedValue="@(() => ProfilePreselection.FromStoredValue(this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedProfile))" Data="@ConfigurationSelectDataFactory.GetComponentProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.VisualBriefing.PreselectedProfile = value)"/>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">@T("Close")</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,6 @@
namespace AIStudio.Dialogs.Settings;
/// <summary>
/// Provides the code-behind type for Visual Briefing default settings.
/// </summary>
public partial class SettingsDialogVisualBriefing : SettingsDialogBase;
@@ -312,7 +312,7 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
}
catch (Exception e)
{
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");;
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
}
}
@@ -0,0 +1,19 @@
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudJustifiedText Typo="Typo.body1">
@this.Message
</MudJustifiedText>
</DialogContent>
<DialogActions>
@if (!string.IsNullOrWhiteSpace(this.ReleaseUrl))
{
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.ReleaseUrl" Target="_blank">
@T("Show me the latest release page")
</MudButton>
}
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,19 @@
using AIStudio.Components;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class UpdateInstructionsDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public string Message { get; set; } = string.Empty;
[Parameter]
public string? ReleaseUrl { get; set; }
private void Close() => this.MudDialog.Close();
}