From f97612de0efe26c05afa84701e5ecd89de6a426b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 4 Apr 2025 16:52:33 +0200 Subject: [PATCH] Show LLM provider issues to the user (#384) --- .../Layout/MainLayout.razor.cs | 8 ++- .../Provider/BaseProvider.cs | 49 ++++++++++++++++++- app/MindWork AI Studio/Tools/Error.cs | 16 ++++++ app/MindWork AI Studio/Tools/Event.cs | 1 + app/MindWork AI Studio/Tools/MessageBus.cs | 4 +- .../wwwroot/changelog/v0.9.39.md | 1 + 6 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/Error.cs diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index 04abf62c..b111898e 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -107,7 +107,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis // Register this component with the message bus: this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED ]); + this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR ]); // Set the snackbar for the update service: UpdateService.SetBlazorDependencies(this.Snackbar); @@ -170,6 +170,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis case Event.COLOR_THEME_CHANGED: this.StateHasChanged(); break; + + case Event.SHOW_ERROR: + if (data is Error error) + error.Show(this.Snackbar); + + break; } } diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 3ad4e8f2..7b24c8bf 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -116,9 +116,50 @@ public abstract class BaseProvider : IProvider, ISecretId response = nextResponse; break; } + + if (nextResponse.StatusCode is HttpStatusCode.Forbidden) + { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Block, $"Tried to communicate with the LLM provider '{this.InstanceName}'. You might not be able to use this provider from your location. The provider message is: '{nextResponse.ReasonPhrase}'")); + this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}')."); + errorMessage = nextResponse.ReasonPhrase; + break; + } if(nextResponse.StatusCode is HttpStatusCode.BadRequest) { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The required message format might be changed. The provider message is: '{nextResponse.ReasonPhrase}'")); + this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}')."); + errorMessage = nextResponse.ReasonPhrase; + break; + } + + if(nextResponse.StatusCode is HttpStatusCode.NotFound) + { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. Something was not found. The provider message is: '{nextResponse.ReasonPhrase}'")); + this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}')."); + errorMessage = nextResponse.ReasonPhrase; + break; + } + + if(nextResponse.StatusCode is HttpStatusCode.Unauthorized) + { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Key, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The API key might be invalid. The provider message is: '{nextResponse.ReasonPhrase}'")); + this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}')."); + errorMessage = nextResponse.ReasonPhrase; + break; + } + + if(nextResponse.StatusCode is HttpStatusCode.InternalServerError) + { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The server might be down or having issues. The provider message is: '{nextResponse.ReasonPhrase}'")); + this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}')."); + errorMessage = nextResponse.ReasonPhrase; + break; + } + + if(nextResponse.StatusCode is HttpStatusCode.ServiceUnavailable) + { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The provider is overloaded. The message is: '{nextResponse.ReasonPhrase}'")); this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}')."); errorMessage = nextResponse.ReasonPhrase; break; @@ -134,8 +175,11 @@ public abstract class BaseProvider : IProvider, ISecretId } if(retry >= MAX_RETRIES || !string.IsNullOrWhiteSpace(errorMessage)) + { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. Even after {MAX_RETRIES} retries, there were some problems with the request. The provider message is: '{errorMessage}'")); return new HttpRateLimitedStreamResult(false, true, errorMessage ?? $"Failed after {MAX_RETRIES} retries; no provider message available", response); - + } + return new HttpRateLimitedStreamResult(true, false, string.Empty, response); } @@ -160,6 +204,7 @@ public abstract class BaseProvider : IProvider, ISecretId } catch(Exception e) { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, $"Tried to communicate with the LLM provider '{this.InstanceName}'. There were some problems with the request. The provider message is: '{e.Message}'")); this.logger.LogError($"Failed to stream chat completion from {providerName} '{this.InstanceName}': {e.Message}"); } @@ -176,6 +221,7 @@ public abstract class BaseProvider : IProvider, ISecretId } catch (Exception e) { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, $"Tried to stream the LLM provider '{this.InstanceName}' answer. There were some problems with the stream. The message is: '{e.Message}'")); this.logger.LogWarning($"Failed to read the end-of-stream state from {providerName} '{this.InstanceName}': {e.Message}"); break; } @@ -196,6 +242,7 @@ public abstract class BaseProvider : IProvider, ISecretId } catch (Exception e) { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, $"Tried to stream the LLM provider '{this.InstanceName}' answer. Was not able to read the stream. The message is: '{e.Message}'")); this.logger.LogError($"Failed to read the stream from {providerName} '{this.InstanceName}': {e.Message}"); break; } diff --git a/app/MindWork AI Studio/Tools/Error.cs b/app/MindWork AI Studio/Tools/Error.cs new file mode 100644 index 00000000..a3ba6c61 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Error.cs @@ -0,0 +1,16 @@ +namespace AIStudio.Tools; + +public readonly record struct Error(string Icon, string Message) +{ + public void Show(ISnackbar snackbar) + { + var icon = this.Icon; + snackbar.Add(this.Message, Severity.Error, config => + { + config.Icon = icon; + config.IconSize = Size.Large; + config.HideTransitionDuration = 600; + config.VisibleStateDuration = 14_000; + }); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs index 38235c8e..57758589 100644 --- a/app/MindWork AI Studio/Tools/Event.cs +++ b/app/MindWork AI Studio/Tools/Event.cs @@ -9,6 +9,7 @@ public enum Event CONFIGURATION_CHANGED, COLOR_THEME_CHANGED, PLUGINS_RELOADED, + SHOW_ERROR, // Update events: USER_SEARCH_FOR_UPDATE, diff --git a/app/MindWork AI Studio/Tools/MessageBus.cs b/app/MindWork AI Studio/Tools/MessageBus.cs index 44e1dc86..06a2dfd8 100644 --- a/app/MindWork AI Studio/Tools/MessageBus.cs +++ b/app/MindWork AI Studio/Tools/MessageBus.cs @@ -65,7 +65,9 @@ public sealed class MessageBus this.sendingSemaphore.Release(); } } - + + public Task SendError(Error error) => this.SendMessage(null, Event.SHOW_ERROR, error); + public void DeferMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data = default) { if (this.deferredMessages.TryGetValue(triggeredEvent, out var queue)) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md index 5f5b26b8..c3b306c2 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md @@ -1,4 +1,5 @@ # v0.9.39, build 214 (2025-04-xx xx:xx UTC) +- Added UI for error handling to display any LLM provider issues. - Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. Please note that this feature is still in development; there are no plugins available yet. - Added the Lua library we use for the plugin system to the about page. - Added the plugin overview page. This page shows all installed plugins and allows you to enable or disable them. It is only available when the plugin preview feature is enabled.