From eb80a0df9594e17c1356094893e7d184380e34dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= <20603780+peerschuett@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:29:48 +0200 Subject: [PATCH 1/4] Added model availability check and error handling for unavailable models --- .../Assistants/I18N/allTexts.lua | 3 + app/MindWork AI Studio/Chat/ContentText.cs | 70 ++++++++++++++++++- .../plugin.lua | 3 + .../plugin.lua | 3 + 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 6d4cc501c..715f64af1 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -1732,6 +1732,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, kee -- Export Chat to Microsoft Word UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word" +-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings. +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings." + -- The local image file does not exist. Skipping the image. UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "The local image file does not exist. Skipping the image." diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs index 3a9b8f9d4..4c28d5301 100644 --- a/app/MindWork AI Studio/Chat/ContentText.cs +++ b/app/MindWork AI Studio/Chat/ContentText.cs @@ -3,6 +3,8 @@ using AIStudio.Provider; using AIStudio.Settings; +using AIStudio.Tools; +using AIStudio.Tools.PluginSystem; using AIStudio.Tools.RAG.RAGProcesses; namespace AIStudio.Chat; @@ -13,6 +15,7 @@ namespace AIStudio.Chat; public sealed class ContentText : IContent { private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ContentText).Namespace, nameof(ContentText)); /// /// The minimum time between two streaming events, when the user @@ -48,11 +51,21 @@ public sealed class ContentText : IContent public async Task CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastUserPrompt, ChatThread? chatThread, CancellationToken token = default) { if(chatThread is null) + { + await this.CompleteWithoutStreaming(); return new(); + } if(!chatThread.IsLLMProviderAllowed(provider)) { LOGGER.LogError("The provider is not allowed for this chat thread due to data security reasons. Skipping the AI process."); + await this.CompleteWithoutStreaming(); + return chatThread; + } + + if(!await this.CheckSelectedModelAvailability(provider, chatModel, token)) + { + await this.CompleteWithoutStreaming(); return chatThread; } @@ -137,6 +150,61 @@ await Task.Run(async () => return chatThread; } + private async Task CompleteWithoutStreaming() + { + this.InitialRemoteWait = false; + this.IsStreaming = false; + await this.StreamingDone(); + } + + private static bool ModelsMatch(Model modelA, Model modelB) + { + var idA = modelA.Id.Trim(); + var idB = modelB.Id.Trim(); + return string.Equals(idA, idB, StringComparison.OrdinalIgnoreCase); + } + + private async Task CheckSelectedModelAvailability(IProvider provider, Model chatModel, CancellationToken token = default) + { + if(chatModel.IsSystemModel) + return true; + + if(string.IsNullOrWhiteSpace(chatModel.Id)) + return true; + + IEnumerable loadedModels; + try + { + loadedModels = await provider.GetTextModels(token: token); + } + catch (OperationCanceledException) + { + return false; + } + catch (Exception e) + { + LOGGER.LogWarning(e, "Skipping selected model availability check for '{ProviderInstanceName}' (provider={ProviderType}) because the model list could not be loaded.", provider.InstanceName, provider.Provider); + return true; + } + + var availableModels = loadedModels.Where(model => !string.IsNullOrWhiteSpace(model.Id)).ToList(); + if (availableModels.Count == 0) + return true; + + if(availableModels.Any(model => ModelsMatch(model, chatModel))) + return true; + + var message = string.Format( + TB("The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."), + chatModel.Id, + provider.InstanceName, + provider.Provider); + + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, message)); + LOGGER.LogWarning("Skipping AI request because model '{ModelId}' is not available from '{ProviderInstanceName}' (provider={ProviderType}).", chatModel.Id, provider.InstanceName, provider.Provider); + return false; + } + /// public IContent DeepClone() => new ContentText { @@ -214,4 +282,4 @@ public async Task PrepareTextContentForAI() /// The text content. /// public string Text { get; set; } = string.Empty; -} \ No newline at end of file +} 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 e9571a6c8..5cf76278d 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 @@ -1734,6 +1734,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "Nein, b -- Export Chat to Microsoft Word UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Chat in Microsoft Word exportieren" +-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings. +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "Das ausgewählte Modell '{0}' ist bei '{1}' (Anbieter={2}) nicht mehr verfügbar. Bitte passen Sie Ihre Anbietereinstellungen an." + -- The local image file does not exist. Skipping the image. UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "Die lokale Bilddatei existiert nicht. Das Bild wird übersprungen." 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 71f6c65ad..edd72cc67 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 @@ -1734,6 +1734,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, kee -- Export Chat to Microsoft Word UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word" +-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings. +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings." + -- The local image file does not exist. Skipping the image. UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "The local image file does not exist. Skipping the image." From 1b3d9ed8cd377b3de639f3eff0933c5ef776573e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= <20603780+peerschuett@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:38:13 +0200 Subject: [PATCH 2/4] Changelog --- app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md | 1 + 1 file changed, 1 insertion(+) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md index 2da8017ca..5e4e7791d 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md @@ -5,6 +5,7 @@ - Added the ability to format your user prompt in the chat using icons instead of typing Markdown directly. - Added the ability to load a system prompt from a file when creating or editing chat templates. - Added a start-page setting, so AI Studio can now open directly on your preferred page when the app starts. Configuration plugins can also provide and optionally lock this default for organizations. +- Added pre-call validation to check if the selected model exists for the provider before making the request. - Added math rendering in chats for LaTeX display formulas, including block formats such as `$$ ... $$` and `\[ ... \]`. - Released the document analysis assistant after an intense testing phase. - Improved the profile selection for assistants and the chat. You can now explicitly choose between the app default profile, no profile, or a specific profile. From b15134d876d5335aeab158268df9aa2852d444fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= <20603780+peerschuett@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:49:37 +0200 Subject: [PATCH 3/4] Added logging and changed return logic for missing or unavailable AI models in Chat/ContentText.cs --- app/MindWork AI Studio/Chat/ContentText.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs index 4c28d5301..31bf09fe1 100644 --- a/app/MindWork AI Studio/Chat/ContentText.cs +++ b/app/MindWork AI Studio/Chat/ContentText.cs @@ -169,8 +169,12 @@ private async Task CheckSelectedModelAvailability(IProvider provider, Mode if(chatModel.IsSystemModel) return true; - if(string.IsNullOrWhiteSpace(chatModel.Id)) - return true; + if (string.IsNullOrWhiteSpace(chatModel.Id)) + { + LOGGER.LogWarning( + "Skipping AI request because model ID is null or white space."); + return false; + } IEnumerable loadedModels; try @@ -189,8 +193,13 @@ private async Task CheckSelectedModelAvailability(IProvider provider, Mode var availableModels = loadedModels.Where(model => !string.IsNullOrWhiteSpace(model.Id)).ToList(); if (availableModels.Count == 0) - return true; - + { + LOGGER.LogWarning( + "Skipping AI request because there are no models available from '{ProviderInstanceName}' (provider={ProviderType}).", + provider.InstanceName, provider.Provider); + return false; + } + if(availableModels.Any(model => ModelsMatch(model, chatModel))) return true; From 4385c9d2c0629eca4af8ef62ec336e595561eec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= <20603780+peerschuett@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:53:14 +0200 Subject: [PATCH 4/4] Formatting changes --- app/MindWork AI Studio/Chat/ContentText.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs index 31bf09fe1..a48e53d55 100644 --- a/app/MindWork AI Studio/Chat/ContentText.cs +++ b/app/MindWork AI Studio/Chat/ContentText.cs @@ -171,8 +171,7 @@ private async Task CheckSelectedModelAvailability(IProvider provider, Mode if (string.IsNullOrWhiteSpace(chatModel.Id)) { - LOGGER.LogWarning( - "Skipping AI request because model ID is null or white space."); + LOGGER.LogWarning("Skipping AI request because model ID is null or white space."); return false; } @@ -194,9 +193,7 @@ private async Task CheckSelectedModelAvailability(IProvider provider, Mode var availableModels = loadedModels.Where(model => !string.IsNullOrWhiteSpace(model.Id)).ToList(); if (availableModels.Count == 0) { - LOGGER.LogWarning( - "Skipping AI request because there are no models available from '{ProviderInstanceName}' (provider={ProviderType}).", - provider.InstanceName, provider.Provider); + LOGGER.LogWarning("Skipping AI request because there are no models available from '{ProviderInstanceName}' (provider={ProviderType}).", provider.InstanceName, provider.Provider); return false; }