diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 1ec8c022..4908fbb6 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -2101,6 +2101,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T14695 -- Add Embedding UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Add Embedding" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1760715963"] = "Uses the provider-configured model" + -- Are you sure you want to delete the embedding provider '{0}'? UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1825371968"] = "Are you sure you want to delete the embedding provider '{0}'?" @@ -2164,6 +2167,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T162847 -- Description UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Description" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Uses the provider-configured model" + -- Add Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider" @@ -2206,9 +2212,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T291173 -- Configured LLM Providers UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Configured LLM Providers" --- as selected by provider -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider" - -- Edit UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3267849393"] = "Edit" @@ -2266,6 +2269,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T14 -- Add transcription provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1645238629"] = "Add transcription provider" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model" + -- Add Transcription Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider" @@ -3205,6 +3211,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Cur -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host" @@ -3412,12 +3421,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert -- Show available models UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models" +-- This host uses the model configured at the provider level. No model selection is available. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available." + -- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually." -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host" @@ -4633,9 +4648,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] = -- Please enter a transcription model name. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Please enter a transcription model name." +-- This host uses the model configured at the provider level. No model selection is available. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available." + -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Model selection" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host" diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor index 4ffc743f..874bc3c9 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor @@ -35,7 +35,7 @@ @context.Num @context.Name @context.UsedLLMProvider.ToName() - @GetEmbeddingProviderModelName(context) + @this.GetEmbeddingProviderModelName(context) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs index 50ebeb13..94878987 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs @@ -15,8 +15,12 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase [Parameter] public EventCallback>> AvailableEmbeddingProvidersChanged { get; set; } - private static string GetEmbeddingProviderModelName(EmbeddingProvider provider) + private string GetEmbeddingProviderModelName(EmbeddingProvider provider) { + // For system models, return localized text: + if (provider.Model.IsSystemModel) + return T("Uses the provider-configured model"); + const int MAX_LENGTH = 36; var modelName = provider.Model.ToString(); return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName; diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor index f9567086..3d359408 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor @@ -1,6 +1,5 @@ @using AIStudio.Provider @using AIStudio.Settings -@using AIStudio.Provider.SelfHosted @inherits SettingsPanelBase @@ -29,20 +28,7 @@ @context.Num @context.InstanceName @context.UsedLLMProvider.ToName() - - @if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED) - { - @GetLLMProviderModelName(context) - } - else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMA_CPP) - { - @GetLLMProviderModelName(context) - } - else - { - @T("as selected by provider") - } - + @this.GetLLMProviderModelName(context) @if (context.IsEnterpriseConfiguration) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs index 035543dc..2272959d 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs @@ -134,8 +134,12 @@ public partial class SettingsPanelProviders : SettingsPanelBase await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); } - private static string GetLLMProviderModelName(AIStudio.Settings.Provider provider) + private string GetLLMProviderModelName(AIStudio.Settings.Provider provider) { + // For system models, return localized text: + if (provider.Model.IsSystemModel) + return T("Uses the provider-configured model"); + const int MAX_LENGTH = 36; var modelName = provider.Model.ToString(); return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName; diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor index 82421e94..0405d6cd 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor @@ -32,7 +32,7 @@ @context.Num @context.Name @context.UsedLLMProvider.ToName() - @GetTranscriptionProviderModelName(context) + @this.GetTranscriptionProviderModelName(context) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs index d564d5cd..243200a3 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs @@ -15,8 +15,12 @@ public partial class SettingsPanelTranscription : SettingsPanelBase [Parameter] public EventCallback>> AvailableTranscriptionProvidersChanged { get; set; } - private static string GetTranscriptionProviderModelName(TranscriptionProvider provider) + private string GetTranscriptionProviderModelName(TranscriptionProvider provider) { + // For system models, return localized text: + if (provider.Model.IsSystemModel) + return T("Uses the provider-configured model"); + const int MAX_LENGTH = 36; var modelName = provider.Model.ToString(); return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName; diff --git a/app/MindWork AI Studio/Components/VoiceRecorder.razor.cs b/app/MindWork AI Studio/Components/VoiceRecorder.razor.cs index d29e28dd..71f3d602 100644 --- a/app/MindWork AI Studio/Components/VoiceRecorder.razor.cs +++ b/app/MindWork AI Studio/Components/VoiceRecorder.razor.cs @@ -242,7 +242,7 @@ public partial class VoiceRecorder : MSGComponentBase { this.Logger.LogWarning( "The configured transcription provider '{ProviderName}' has a confidence level of '{ProviderLevel}', which is below the minimum required level of '{MinimumLevel}'.", - transcriptionProviderSettings.Name, + transcriptionProviderSettings.UsedLLMProvider, providerConfidence.Level, minimumLevel); await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("The configured transcription provider does not meet the minimum confidence level."))); @@ -259,7 +259,7 @@ public partial class VoiceRecorder : MSGComponentBase } // Call the transcription API: - this.Logger.LogInformation("Starting transcription with provider '{ProviderName}' and model '{ModelName}'.", transcriptionProviderSettings.Name, transcriptionProviderSettings.Model.DisplayName); + this.Logger.LogInformation("Starting transcription with provider '{ProviderName}' and model '{ModelName}'.", transcriptionProviderSettings.UsedLLMProvider, transcriptionProviderSettings.Model.ToString()); var transcribedText = await provider.TranscribeAudioAsync(transcriptionProviderSettings.Model, this.finalRecordingPath, this.SettingsManager); if (string.IsNullOrWhiteSpace(transcribedText)) diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor index aaf2cec7..85e6e6ef 100644 --- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor @@ -44,7 +44,7 @@ @if (this.DataLLMProvider.IsHostNeeded()) { - + @foreach (Host host in Enum.GetValues(typeof(Host))) { if (host.IsEmbeddingSupported()) @@ -101,6 +101,12 @@ } } + @if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue)) + { + + @this.dataLoadingModelsIssue + + } @* ReSharper disable once CSharpWarnings::CS8974 *@ diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs index 8a0180c5..6520b7ee 100644 --- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs @@ -71,7 +71,10 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId [Inject] private RustService RustService { get; init; } = null!; - + + [Inject] + private ILogger Logger { get; init; } = null!; + private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); /// @@ -85,7 +88,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId private string dataManuallyModel = string.Empty; private string dataAPIKeyStorageIssue = string.Empty; private string dataEditingPreviousInstanceName = string.Empty; - + private string dataLoadingModelsIssue = string.Empty; + // We get the form reference from Blazor code to validate it manually: private MudForm form = null!; @@ -102,6 +106,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName, GetUsedInstanceNames = () => this.UsedInstanceNames, GetHost = () => this.DataHost, + IsModelProvidedManually = () => this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is Host.OLLAMA, }; } @@ -208,7 +213,16 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId { await this.form.Validate(); this.dataAPIKeyStorageIssue = string.Empty; - + + // Manually validate the model selection (needed when no models are loaded + // and the MudSelect is not rendered): + var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel); + if (!string.IsNullOrWhiteSpace(modelValidationError)) + { + this.dataIssues = [..this.dataIssues, modelValidationError]; + this.dataIsValid = false; + } + // When the data is not valid, we don't store it: if (!this.dataIsValid) return; @@ -250,21 +264,40 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId await this.form.Validate(); } } - + + 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.availableModels.Clear(); + this.dataLoadingModelsIssue = string.Empty; + } + private async Task ReloadModels() { + this.dataLoadingModelsIssue = string.Empty; var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings(); var provider = currentEmbeddingProviderSettings.CreateProvider(); - if(provider is NoProvider) + if (provider is NoProvider) return; - - var models = await provider.GetEmbeddingModels(this.dataAPIKey); - - // Order descending by ID means that the newest models probably come first: - var orderedModels = models.OrderByDescending(n => n.Id); - - this.availableModels.Clear(); - this.availableModels.AddRange(orderedModels); + + try + { + var models = await provider.GetEmbeddingModels(this.dataAPIKey); + + // Order descending by ID means that the newest models probably come first: + var orderedModels = models.OrderByDescending(n => n.Id); + + this.availableModels.Clear(); + this.availableModels.AddRange(orderedModels); + } + catch (Exception e) + { + 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."); + } } private string APIKeyText => this.DataLLMProvider switch diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index b424202d..96e94a2f 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -41,7 +41,7 @@ @if (this.DataLLMProvider.IsHostNeeded()) { - + @foreach (Host host in Enum.GetValues(typeof(Host))) { @if (host.IsChatSupported()) @@ -71,52 +71,69 @@ @* ReSharper restore Asp.Entity *@ } - - - @if (this.DataLLMProvider.IsLLMModelProvidedManually()) - { - - @T("Show available models") - - - } - else - { - - @T("Load models") - - @if(this.availableModels.Count is 0) + @if (!this.DataLLMProvider.IsLLMModelSelectionHidden(this.DataHost)) + { + + + @if (this.DataLLMProvider.IsLLMModelProvidedManually()) { - - @T("No models loaded or available.") - + + @T("Show available models") + + } else { - - @foreach (var model in this.availableModels) - { - - @model - - } - + + @T("Load models") + + @if(this.availableModels.Count is 0) + { + + @T("No models loaded or available.") + + } + else + { + + @foreach (var model in this.availableModels) + { + + @model + + } + + } } + + @if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue)) + { + + @this.dataLoadingModelsIssue + } - - + + } + else + { + + + @T("This host uses the model configured at the provider level. No model selection is available.") + + + } @* ReSharper disable once CSharpWarnings::CS8974 *@ Logger { get; init; } = null!; + private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); /// @@ -97,6 +100,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId private string dataManuallyModel = string.Empty; private string dataAPIKeyStorageIssue = string.Empty; private string dataEditingPreviousInstanceName = string.Empty; + private string dataLoadingModelsIssue = string.Empty; private bool showExpertSettings; // We get the form reference from Blazor code to validate it manually: @@ -115,25 +119,36 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName, GetUsedInstanceNames = () => this.UsedInstanceNames, GetHost = () => this.DataHost, + IsModelProvidedManually = () => this.DataLLMProvider.IsLLMModelProvidedManually(), }; } private AIStudio.Settings.Provider CreateProviderSettings() { var cleanedHostname = this.DataHostname.Trim(); + + // Determine the model based on the provider and host configuration: + Model model; + if (this.DataLLMProvider.IsLLMModelSelectionHidden(this.DataHost)) + { + // Use system model placeholder for hosts that don't support model selection (e.g., llama.cpp): + 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 = this.DataLLMProvider switch - { - LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE => new Model(this.dataManuallyModel, null), - _ => this.DataModel - }, - + Model = model, IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED, IsEnterpriseConfiguration = false, Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname, @@ -222,7 +237,16 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId await this.form.Validate(); if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue)) this.dataAPIKeyStorageIssue = string.Empty; - + + // Manually validate the model selection (needed when no models are loaded + // and the MudSelect is not rendered): + var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel); + if (!string.IsNullOrWhiteSpace(modelValidationError)) + { + this.dataIssues = [..this.dataIssues, modelValidationError]; + this.dataIsValid = false; + } + // When the data is not valid, we don't store it: if (!this.dataIsValid) return; @@ -264,21 +288,40 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId await this.form.Validate(); } } + + 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.availableModels.Clear(); + this.dataLoadingModelsIssue = string.Empty; + } private async Task ReloadModels() { + this.dataLoadingModelsIssue = string.Empty; var currentProviderSettings = this.CreateProviderSettings(); var provider = currentProviderSettings.CreateProvider(); - if(provider is NoProvider) + if (provider is NoProvider) return; - - var models = await provider.GetTextModels(this.dataAPIKey); - - // Order descending by ID means that the newest models probably come first: - var orderedModels = models.OrderByDescending(n => n.Id); - - this.availableModels.Clear(); - this.availableModels.AddRange(orderedModels); + + try + { + var models = await provider.GetTextModels(this.dataAPIKey); + + // Order descending by ID means that the newest models probably come first: + var orderedModels = models.OrderByDescending(n => n.Id); + + this.availableModels.Clear(); + this.availableModels.AddRange(orderedModels); + } + catch (Exception e) + { + 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."); + } } private string APIKeyText => this.DataLLMProvider switch diff --git a/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor b/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor index 0a2aa3bb..78d2dea2 100644 --- a/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor @@ -44,7 +44,7 @@ @if (this.DataLLMProvider.IsHostNeeded()) { - + @foreach (Host host in Enum.GetValues(typeof(Host))) { if (host.IsTranscriptionSupported()) @@ -57,51 +57,68 @@ } - - - @if (this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost)) - { - - } - else - { - - @T("Load") - - @if(this.availableModels.Count is 0) + @if (!this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost)) + { + + + @if (this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost)) { - - @T("No models loaded or available.") - + } else { - - @foreach (var model in this.availableModels) - { - - @model - - } - + + @T("Load") + + @if(this.availableModels.Count is 0) + { + + @T("No models loaded or available.") + + } + else + { + + @foreach (var model in this.availableModels) + { + + @model + + } + + } } + + @if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue)) + { + + @this.dataLoadingModelsIssue + } - - + + } + else + { + + + @T("This host uses the model configured at the provider level. No model selection is available.") + + + } @* ReSharper disable once CSharpWarnings::CS8974 *@ Logger { get; init; } = null!; + private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); /// @@ -85,7 +88,8 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId private string dataManuallyModel = string.Empty; private string dataAPIKeyStorageIssue = string.Empty; private string dataEditingPreviousInstanceName = string.Empty; - + private string dataLoadingModelsIssue = string.Empty; + // We get the form reference from Blazor code to validate it manually: private MudForm form = null!; @@ -102,31 +106,39 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName, GetUsedInstanceNames = () => this.UsedInstanceNames, GetHost = () => this.DataHost, + IsModelProvidedManually = () => this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost), }; } private TranscriptionProvider CreateTranscriptionProviderSettings() { var cleanedHostname = this.DataHostname.Trim(); - Model model = default; - if(this.DataLLMProvider is LLMProviders.SELF_HOSTED) + + // Determine the model based on the provider and host configuration: + Model model; + if (this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost)) + { + // Use system model placeholder for hosts that don't support model selection (e.g., whisper.cpp): + model = Model.SYSTEM_MODEL; + } + else if (this.DataLLMProvider is LLMProviders.SELF_HOSTED) { switch (this.DataHost) { case Host.OLLAMA: model = new Model(this.dataManuallyModel, null); break; - + case Host.VLLM: case Host.LM_STUDIO: - case Host.WHISPER_CPP: + default: model = this.DataModel; break; } } else model = this.DataModel; - + return new() { Num = this.DataNum, @@ -216,7 +228,16 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId { await this.form.Validate(); this.dataAPIKeyStorageIssue = string.Empty; - + + // Manually validate the model selection (needed when no models are loaded + // and the MudSelect is not rendered): + var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel); + if (!string.IsNullOrWhiteSpace(modelValidationError)) + { + this.dataIssues = [..this.dataIssues, modelValidationError]; + this.dataIsValid = false; + } + // When the data is not valid, we don't store it: if (!this.dataIsValid) return; @@ -243,7 +264,7 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId { if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel)) return T("Please enter a transcription model name."); - + return null; } @@ -258,21 +279,40 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId await this.form.Validate(); } } - + + 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.availableModels.Clear(); + this.dataLoadingModelsIssue = string.Empty; + } + private async Task ReloadModels() { + this.dataLoadingModelsIssue = string.Empty; var currentTranscriptionProviderSettings = this.CreateTranscriptionProviderSettings(); var provider = currentTranscriptionProviderSettings.CreateProvider(); - if(provider is NoProvider) + if (provider is NoProvider) return; - - var models = await provider.GetTranscriptionModels(this.dataAPIKey); - - // Order descending by ID means that the newest models probably come first: - var orderedModels = models.OrderByDescending(n => n.Id); - - this.availableModels.Clear(); - this.availableModels.AddRange(orderedModels); + + try + { + var models = await provider.GetTranscriptionModels(this.dataAPIKey); + + // Order descending by ID means that the newest models probably come first: + var orderedModels = models.OrderByDescending(n => n.Id); + + this.availableModels.Clear(); + this.availableModels.AddRange(orderedModels); + } + catch (Exception e) + { + 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."); + } } private string APIKeyText => this.DataLLMProvider switch diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index ebab2f8c..6facc933 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -2103,6 +2103,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T14695 -- Add Embedding UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Einbettung hinzufügen" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell" + -- Are you sure you want to delete the embedding provider '{0}'? UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1825371968"] = "Sind Sie sicher, dass Sie den Einbettungsanbieter '{0}' löschen möchten?" @@ -2166,11 +2169,14 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T162847 -- Description UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Beschreibung" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell" + -- Add Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Anbieter hinzufügen" -- Configure LLM Providers -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1810190350"] = "Anbieter für LLM konfigurieren" +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1810190350"] = "Anbieter für LLMs konfigurieren" -- Edit LLM Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "LLM-Anbieter bearbeiten" @@ -2206,10 +2212,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T284206 UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "Noch keine Anbieter konfiguriert." -- Configured LLM Providers -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Konfigurierte Anbieter für LLM" - --- as selected by provider -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "wie vom Anbieter ausgewählt" +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Konfigurierte Anbieter für LLMs" -- Edit UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3267849393"] = "Bearbeiten" @@ -2268,6 +2271,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T14 -- Add transcription provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1645238629"] = "Anbieter für Transkriptionen hinzufügen" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell" + -- Add Transcription Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Anbieter für Transkriptionen hinzufügen" @@ -2292,6 +2298,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T40 -- Configured Transcription Providers UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4210863523"] = "Konfigurierte Anbieter für Transkriptionen" +-- With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure LLM providers' section. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T584860404"] = "Mit Unterstützung von Modellen für Transkriptionen kann MindWork AI Studio menschliche Sprache in Text umwandeln. Das ist zum Beispiel hilfreich, wenn Sie Texte diktieren möchten. Sie können aus speziellen Modellen für Transkriptionen wählen, jedoch nicht aus multimodalen LLMs (Large Language Models), die sowohl Sprache als auch Text verarbeiten können. Die Einrichtung multimodaler Modelle erfolgt im Abschnitt „Anbieter für LLMs konfigurieren“." + -- This transcription provider is managed by your organization. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T756131076"] = "Dieser Anbieter für Transkriptionen wird von Ihrer Organisation verwaltet." @@ -2301,9 +2310,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78 -- Are you sure you want to delete the transcription provider '{0}'? UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T789660305"] = "Möchten Sie den Anbieter für Transkriptionen „{0}“ wirklich löschen?" --- With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the \"Configure providers\" section. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T584860404"] = "Mit Unterstützung von Modellen für Transkriptionen kann MindWork AI Studio menschliche Sprache in Text umwandeln. Das ist zum Beispiel hilfreich, wenn Sie Texte diktieren möchten. Sie können aus speziellen Modellen für Transkriptionen wählen, jedoch nicht aus multimodalen LLMs (Large Language Models), die sowohl Sprache als auch Text verarbeiten können. Die Einrichtung multimodaler Modelle erfolgt im Abschnitt „Anbieter für LLM konfigurieren“." - -- Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Anbieter" @@ -3207,6 +3213,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Der -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Modellauswahl" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "Wir können derzeit nicht mit dem Anbieter kommunizieren, um Modelle zu laden. Bitte versuchen Sie es später erneut." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host" @@ -3414,12 +3423,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Experten-Ei -- Show available models UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Verfügbare Modelle anzeigen" +-- This host uses the model configured at the provider level. No model selection is available. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Es ist keine Modellauswahl verfügbar." + -- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Derzeit können wir die Modelle für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein." -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Modellauswahl" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "Wir können derzeit nicht mit dem Anbieter kommunizieren, um Modelle zu laden. Bitte versuchen Sie es später erneut." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host" @@ -4635,9 +4650,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] = -- Please enter a transcription model name. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Bitte geben Sie den Namen eines Transkriptionsmodells ein." +-- This host uses the model configured at the provider level. No model selection is available. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Eine Modellauswahl ist nicht verfügbar." + -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Modellauswahl" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T504465522"] = "Wir können derzeit nicht mit dem Anbieter kommunizieren, um Modelle zu laden. Bitte versuchen Sie es später erneut." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host" diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 6e0ba76c..915a5485 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -2103,6 +2103,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T14695 -- Add Embedding UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Add Embedding" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1760715963"] = "Uses the provider-configured model" + -- Are you sure you want to delete the embedding provider '{0}'? UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1825371968"] = "Are you sure you want to delete the embedding provider '{0}'?" @@ -2166,6 +2169,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T162847 -- Description UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Description" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Uses the provider-configured model" + -- Add Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider" @@ -2208,9 +2214,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T291173 -- Configured LLM Providers UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Configured LLM Providers" --- as selected by provider -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider" - -- Edit UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3267849393"] = "Edit" @@ -2268,6 +2271,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T14 -- Add transcription provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1645238629"] = "Add transcription provider" +-- Uses the provider-configured model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model" + -- Add Transcription Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider" @@ -2292,7 +2298,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T40 -- Configured Transcription Providers UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4210863523"] = "Configured Transcription Providers" --- With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure providers' section. +-- With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure LLM providers' section. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T584860404"] = "With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure LLM providers' section." -- This transcription provider is managed by your organization. @@ -3207,6 +3213,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Cur -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host" @@ -3414,12 +3423,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert -- Show available models UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models" +-- This host uses the model configured at the provider level. No model selection is available. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available." + -- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually." -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host" @@ -4635,9 +4650,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] = -- Please enter a transcription model name. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Please enter a transcription model name." +-- This host uses the model configured at the provider level. No model selection is available. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available." + -- Model selection UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Model selection" +-- We are currently unable to communicate with the provider to load models. Please try again later. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later." + -- Host UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host" diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 3e207da5..4dd27763 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -554,10 +554,22 @@ public abstract class BaseProvider : IProvider, ISecretId await using var fileStream = File.OpenRead(audioFilePath); using var fileContent = new StreamContent(fileStream); + + // Set the content type based on the file extension: fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType); - + + // Add the file content to the form data: form.Add(fileContent, "file", Path.GetFileName(audioFilePath)); - form.Add(new StringContent(transcriptionModel.Id), "model"); + + // + // Add the model name to the form data. Ensure that a model name is always provided. + // Otherwise, the StringContent constructor will throw an exception. + // + var modelName = transcriptionModel.Id; + if (string.IsNullOrWhiteSpace(modelName)) + modelName = "placeholder"; + + form.Add(new StringContent(modelName), "model"); using var request = new HttpRequestMessage(HttpMethod.Post, host.TranscriptionURL()); request.Content = form; diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index ea548923..ffaa0d06 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -327,6 +327,32 @@ public static class LLMProvidersExtensions _ => false, }; + /// + /// Determines if the model selection should be completely hidden for LLM providers. + /// This is the case when the host does not support model selection (e.g., llama.cpp). + /// + /// The provider. + /// The host for self-hosted providers. + /// True if model selection should be hidden; otherwise, false. + public static bool IsLLMModelSelectionHidden(this LLMProviders provider, Host host) => provider switch + { + LLMProviders.SELF_HOSTED => host is Host.LLAMA_CPP, + _ => false, + }; + + /// + /// Determines if the model selection should be completely hidden for transcription providers. + /// This is the case when the host does not support model selection (e.g., whisper.cpp). + /// + /// The provider. + /// The host for self-hosted providers. + /// True if model selection should be hidden; otherwise, false. + public static bool IsTranscriptionModelSelectionHidden(this LLMProviders provider, Host host) => provider switch + { + LLMProviders.SELF_HOSTED => host is Host.WHISPER_CPP, + _ => false, + }; + public static bool IsHostNeeded(this LLMProviders provider) => provider switch { LLMProviders.SELF_HOSTED => true, @@ -391,13 +417,13 @@ public static class LLMProvidersExtensions { case Host.NONE: case Host.LLAMA_CPP: + case Host.WHISPER_CPP: default: return false; case Host.OLLAMA: case Host.LM_STUDIO: case Host.VLLM: - case Host.WHISPER_CPP: return true; } } diff --git a/app/MindWork AI Studio/Provider/Model.cs b/app/MindWork AI Studio/Provider/Model.cs index 4e582f97..0cd43395 100644 --- a/app/MindWork AI Studio/Provider/Model.cs +++ b/app/MindWork AI Studio/Provider/Model.cs @@ -9,6 +9,22 @@ namespace AIStudio.Provider; /// The model's display name. public readonly record struct Model(string Id, string? DisplayName) { + /// + /// Special model ID used when the model is selected by the system/host + /// and cannot be changed by the user (e.g., llama.cpp, whisper.cpp). + /// + private const string SYSTEM_MODEL_ID = "::system::"; + + /// + /// Creates a system-configured model placeholder. + /// + public static readonly Model SYSTEM_MODEL = new(SYSTEM_MODEL_ID, null); + + /// + /// Checks if this model is the system-configured placeholder. + /// + public bool IsSystemModel => this == SYSTEM_MODEL; + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(Model).Namespace, nameof(Model)); #region Overrides of ValueType diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index d63ec0d1..7cfb236b 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -149,31 +149,30 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide } /// - public override Task> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default) + public override async Task> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default) { try { switch (host) { case Host.WHISPER_CPP: - return Task.FromResult>( - new List - { - new("loaded-model", TB("Model as configured by whisper.cpp")), - }); + return new List + { + new("loaded-model", TB("Model as configured by whisper.cpp")), + }; case Host.OLLAMA: case Host.VLLM: - return this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, [], [], token, apiKeyProvisional); + return await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, [], [], token, apiKeyProvisional); default: - return Task.FromResult(Enumerable.Empty()); + return []; } } catch (Exception e) { - LOGGER.LogError(e, "Failed to load transcription models from self-hosted provider."); - return Task.FromResult(Enumerable.Empty()); + LOGGER.LogError($"Failed to load transcription models from self-hosted provider: {e.Message}"); + return []; } } diff --git a/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs b/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs index 3b074ba9..bb72feb4 100644 --- a/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs +++ b/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs @@ -19,7 +19,9 @@ public sealed class ProviderValidation public Func> GetUsedInstanceNames { get; init; } = () => []; public Func GetHost { get; init; } = () => Host.NONE; - + + public Func IsModelProvidedManually { get; init; } = () => false; + public string? ValidatingHostname(string hostname) { if(this.GetProvider() != LLMProviders.SELF_HOSTED) @@ -70,12 +72,22 @@ public sealed class ProviderValidation public string? ValidatingModel(Model model) { - if(this.GetProvider() is LLMProviders.SELF_HOSTED && this.GetHost() == Host.LLAMA_CPP) + // For NONE providers, no validation is needed: + if (this.GetProvider() is LLMProviders.NONE) return null; - + + // For self-hosted llama.cpp or whisper.cpp, no model selection needed + // (model is loaded at startup): + if (this.GetProvider() is LLMProviders.SELF_HOSTED && this.GetHost() is Host.LLAMA_CPP or Host.WHISPER_CPP) + return null; + + // For manually entered models, this validation doesn't apply: + if (this.IsModelProvidedManually()) + return null; + if (model == default) return TB("Please select a model."); - + return null; } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md b/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md index a1de1efb..bdf0faa9 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.1.2.md @@ -1,5 +1,10 @@ # v26.1.2, build 232 (2026-01-xx xx:xx UTC) - Added the option to hide specific assistants by configuration plugins. This is useful for enterprise environments in organizations. +- Improved error handling for model loading in provider dialogs (LLMs, embeddings, transcriptions). - Improved the microphone handling (transcription preview) so that all sound effects and the voice recording are processed without interruption. +- Improved the handling of self-hosted providers in the configuration dialogs (LLMs, embeddings, and transcriptions) when the host cannot provide a list of models. - Fixed a logging bug that prevented log events from being recorded in some cases. +- Fixed a bug that allowed adding a provider (LLM, embedding, or transcription) without selecting a model. +- Fixed a bug with local transcription providers by handling errors correctly when the local provider is unavailable. +- Fixed a bug with local transcription providers by correctly handling empty model IDs. - Fixed a bug affecting the transcription preview: previously, when you stopped music or other media, recorded or dictated text, and then tried to resume playback, the media wouldn’t resume as expected. This behavior is now fixed. \ No newline at end of file