feat(ai-chat): surface the real cause in the error banner

The AI chat error banner always showed a generic "Something went wrong"
with no reason. The server already forwards the provider cause into the
stream (e.g. "Cannot connect to API: read ECONNRESET"), but the client
hid it behind a static heading.

- describeChatError now returns { title, detail }: a short heading naming
  the cause category plus a one-line explanation.
- Add classifyProviderError: maps connection reset, timeout, rate limit,
  context-window overflow, quota and auth failures to clear categories;
  the 403/503 gating responses are preserved; unknown errors fall back to
  the verbatim provider text.
- Match HTTP status codes only as the leading token and textual signatures
  only against the message head (before "| response body:"), so a number
  or phrase in the response-body snippet never mislabels the cause.
- Use the new {title, detail} in all three banners: chat-thread,
  share-ai-widget and the persisted-error banner in message-item.
- Cover the classifier with 20 unit tests (categories + regressions).
This commit is contained in:
claude_code
2026-06-21 18:54:43 +03:00
parent 0cfc3c8f89
commit 0bbf94c154
5 changed files with 310 additions and 48 deletions

View File

@@ -175,6 +175,11 @@ export default function ChatThread({
const isStreaming = status === "submitted" || status === "streaming";
// Classify the turn error into a heading + detail so the banner names the cause
// (connection reset, timeout, rate limit, context overflow, quota, ...) instead
// of a generic "Something went wrong".
const errorView = error ? describeChatError(error.message ?? "", t) : null;
// Clicking a role card both binds the role to THIS new chat and immediately
// starts the conversation. roleIdRef is set synchronously here because the
// parent's selectedRoleId state update would only reach roleIdRef on the next
@@ -198,15 +203,15 @@ export default function ChatThread({
assistantName={assistantName}
/>
{error && (
{errorView && (
<Alert
variant="light"
color="red"
icon={<IconAlertTriangle size={16} />}
mb="xs"
title={t("Something went wrong")}
title={errorView.title}
>
{describeChatError(error.message ?? "", t)}
{errorView.detail}
</Alert>
)}