Show LLM provider issues to the user

Closes https://github.com/MindWorkAI/Planning/issues/2
This commit is contained in:
Thorsten Sommer 2025-04-04 16:51:53 +02:00
parent c56232cba7
commit 08b2a10f1a
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
6 changed files with 76 additions and 3 deletions

View File

@ -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;
}
}

View File

@ -117,8 +117,49 @@ public abstract class BaseProvider : IProvider, ISecretId
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,7 +175,10 @@ 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;
}

View File

@ -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;
});
}
}

View File

@ -9,6 +9,7 @@ public enum Event
CONFIGURATION_CHANGED,
COLOR_THEME_CHANGED,
PLUGINS_RELOADED,
SHOW_ERROR,
// Update events:
USER_SEARCH_FOR_UPDATE,

View File

@ -66,6 +66,8 @@ public sealed class MessageBus
}
}
public Task SendError(Error error) => this.SendMessage(null, Event.SHOW_ERROR, error);
public void DeferMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data = default)
{
if (this.deferredMessages.TryGetValue(triggeredEvent, out var queue))

View File

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