Sorted every model list by what a model is made for (#984)
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:
Thorsten Sommer authored and GitHub committed 2026-09-19 17:54:10 +02:00
1 parent e42277beba
commit f9c6075c50
36 files changed
+947 -333

No files matched your search

@@ -133,10 +133,11 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private string[] dataIssues = [];
private string dataAPIKey = string.Empty;
private bool dataHadStoredAPIKeyOnLoad;
private string dataManuallyModel = string.Empty;
private string dataAPIKeyStorageIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
private string dataLoadingModelsIssue = string.Empty;
private bool dataConfiguredModelIsNotOffered;
private bool dataServerWasAskedForItsModels;
private string dataFilePath = string.Empty;
private string dataTokenizerFingerprint = string.Empty;
private string dataCustomTokenizerValidationIssue = string.Empty;
@@ -162,7 +163,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
GetUsedInstanceNames = () => this.UsedInstanceNames,
GetHost = () => this.DataHost,
IsModelProvidedManually = () => this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost),
GetCustomTokenizerValidationIssue = () => this.dataCustomTokenizerValidationIssue,
};
}
@@ -170,24 +170,13 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private EmbeddingProvider CreateEmbeddingProviderSettings()
{
var cleanedHostname = this.DataHostname.Trim();
Model model = default;
if(this.DataLLMProvider is LLMProviders.SELF_HOSTED)
{
if (this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost))
model = new Model(this.dataManuallyModel, null);
else if (this.DataHost is Host.LM_STUDIO)
model = this.DataModel;
}
else
model = this.DataModel;
return new()
{
Num = this.DataNum,
Id = this.DataId,
Name = this.DataName,
UsedLLMProvider = this.DataLLMProvider,
Model = model,
Model = this.DataModel,
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
Host = this.DataHost,
@@ -225,10 +214,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|| this.DataTokenLimit != EmbeddingProvider.DEFAULT_TOKEN_LIMIT
|| this.DataEmbeddingBatchSize != EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
// When using self-hosted embedding, we must copy the model name:
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
this.dataManuallyModel = this.DataModel.Id;
// Load the API key. A self-hosted server may well need one: LM Studio can ask for a
// token of its own, and any of these servers can sit behind an authenticating proxy.
// So we try for every host and treat a missing key as the normal case (isTrying).
@@ -358,14 +343,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
}
private string? ValidateManuallyModel(string manuallyModel)
{
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel))
return T("Please enter an embedding model name.");
return null;
}
private string? ValidateTokenLimit(int tokenLimit)
{
if (tokenLimit < 1)
@@ -516,9 +493,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
// When the host changes, reset the model selection state:
this.DataHost = selectedHost;
this.DataModel = default;
this.dataManuallyModel = string.Empty;
this.availableModels.Clear();
this.dataLoadingModelsIssue = string.Empty;
this.dataConfiguredModelIsNotOffered = false;
}
/// <summary>
@@ -535,11 +512,13 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
this.DataModel = default;
this.availableModels.Clear();
this.dataLoadingModelsIssue = string.Empty;
this.dataConfiguredModelIsNotOffered = false;
}
private async Task ReloadModels()
{
this.dataLoadingModelsIssue = string.Empty;
this.dataServerWasAskedForItsModels = true;
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
var provider = currentEmbeddingProviderSettings.CreateProvider();
if (provider is NoProvider)
@@ -562,8 +541,57 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
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.");
}
// Whatever the server answered, and whether it answered at all, the model this provider was
// configured with stays on the list:
this.PinConfiguredModel();
}
/// <summary>
/// Keeps the configured model selectable, also when the server does not offer it right now.
/// </summary>
/// <remarks>
/// This is deliberately the opposite of what the chat provider dialog does, which replaces the
/// configured model with the one the server reported. An embedding provider carries indexed data
/// sources, and its model ID is part of the embedding signature: changing it -- even only in its
/// spelling -- means every prepared document is prepared again. So the stored model is added to
/// the list here rather than the list being applied to the stored model. A model nobody serves
/// any more stays visible and stays chosen, and changing it stays the user's decision, which
/// storing then asks about.
///
/// Comparing is what Model does, which is by ID and ordinal. Matching a differing spelling would
/// mean writing that other spelling into the settings, and that is the very change this avoids.
/// </remarks>
private void PinConfiguredModel()
{
if (string.IsNullOrWhiteSpace(this.DataModel.Id))
{
this.dataConfiguredModelIsNotOffered = false;
return;
}
this.dataConfiguredModelIsNotOffered = !this.availableModels.Contains(this.DataModel);
if (this.dataConfiguredModelIsNotOffered)
this.availableModels.Insert(0, this.DataModel);
}
/// <summary>
/// Whether the server answered without naming a single embedding model.
/// </summary>
/// <remarks>
/// Two situations end up here, and the user is the only one who can tell them apart: a server
/// running no embedding model at all, and one running an embedding model under a name no rule
/// covers. Saying so beats the bare "No models loaded or available.", which reads like a
/// failure and leaves nobody anywhere to go -- the field for typing a name is gone, on purpose.
/// Describing such a model in a model plugin is the way out, and naming it here is what turns
/// a dead end into one.
/// </remarks>
private bool ServerNamedNoEmbeddingModel =>
this.DataLLMProvider is LLMProviders.SELF_HOSTED &&
this.dataServerWasAskedForItsModels &&
string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue) &&
this.availableModels.Count is 0;
private string APIKeyText => this.DataLLMProvider switch
{
LLMProviders.SELF_HOSTED => T("(Optional) API Key"),