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

@@ -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,