From f71b6af19aaec5022882d9b97e401de0c983a3d1 Mon Sep 17 00:00:00 2001 From: "cyrus@tinyhumans.ai" Date: Thu, 21 May 2026 01:10:03 +0530 Subject: [PATCH 1/2] fix(settings): wrap provider setup errors (#2363) --- .../components/settings/panels/AIPanel.tsx | 107 ++++++++++++++++-- .../panels/__tests__/AIPanel.test.tsx | 64 ++++++++++- 2 files changed, 160 insertions(+), 11 deletions(-) diff --git a/app/src/components/settings/panels/AIPanel.tsx b/app/src/components/settings/panels/AIPanel.tsx index 05fb6d91a6..9f1e18b9b5 100644 --- a/app/src/components/settings/panels/AIPanel.tsx +++ b/app/src/components/settings/panels/AIPanel.tsx @@ -448,6 +448,88 @@ function useInstalledModels(snapshot: LocalProviderSnapshot | null): OllamaModel // Primitives // ───────────────────────────────────────────────────────────────────────────── +type ProviderErrorPresentation = { summary: string; details: string }; + +function decodeJsonString(value: string): string { + try { + return JSON.parse(`"${value}"`) as string; + } catch { + return value; + } +} + +function findProviderJsonMessage(raw: string): string | null { + const match = raw.match(/"message"\s*:\s*"((?:\\.|[^"\\])*)"/); + return match ? decodeJsonString(match[1]) : null; +} + +function cleanProviderMessage(message: string): string { + return message.replace(/\s+/g, ' ').trim(); +} + +function presentProviderSetupError(raw: string): ProviderErrorPresentation { + const details = raw.trim() || 'Provider setup failed.'; + const couldNotReach = details.match(/^Could not reach\s+([^:]+):\s*(.*)$/i); + const provider = couldNotReach?.[1]?.trim(); + const cause = couldNotReach?.[2]?.trim() || details; + const status = cause.match(/provider returned\s+(\d{3})/i)?.[1]; + const providerLabel = provider || 'The provider'; + + let summary: string | null = null; + + if (status === '401' || status === '403') { + summary = `${providerLabel} rejected the credentials. Check the API key and try again.`; + } else if (status === '404') { + summary = `${providerLabel} did not recognize the endpoint. Check the base URL and try again.`; + } else if (status && Number(status) >= 500) { + summary = `${providerLabel} is unavailable right now. Try again or check the provider status.`; + } else if (/HTTP request failed|error sending request|timed out|ECONNREFUSED/i.test(cause)) { + summary = `Could not reach ${providerLabel}. Check the endpoint URL and network connection, then try again.`; + } + + if (!summary) { + const jsonMessage = findProviderJsonMessage(cause); + if (jsonMessage) { + summary = provider + ? `Could not reach ${provider}: ${cleanProviderMessage(jsonMessage)}` + : cleanProviderMessage(jsonMessage); + } + } + + if (!summary) { + summary = cleanProviderMessage(cause); + } + + if (summary.length > 220) { + summary = `${summary.slice(0, 217).trimEnd()}...`; + } + + return { summary, details }; +} + +const ProviderSetupErrorNotice = ({ error }: { error: string }) => { + const { summary, details } = presentProviderSetupError(error); + const hasDetails = details !== summary; + + return ( +
+

{summary}

+ {hasDetails ? ( +
+ + Technical details + +
+            {details}
+          
+
+ ) : null} +
+ ); +}; + // SectionLabel removed alongside its only call site (the old // "Cloud providers" / "Local provider" headings). @@ -577,7 +659,13 @@ const ProviderKeyDialog = ({ try { await onSubmit(trimmed); } catch (err) { - setError(err instanceof Error ? err.message : String(err)); + const message = err instanceof Error ? err.message : String(err); + console.warn('[ai-settings] provider setup failed', { + slug, + local_runtime: isLocalRuntime, + summary: presentProviderSetupError(message).summary, + }); + setError(message); setPhase('idle'); } }; @@ -619,9 +707,7 @@ const ProviderKeyDialog = ({ }} className={`rounded-lg border border-stone-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 placeholder-stone-400 dark:placeholder-neutral-500 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 disabled:opacity-60 ${isLocalRuntime ? 'font-mono' : ''}`} /> - {error ? ( -

{error}

- ) : null} + {error ? : null}
@@ -2617,11 +2703,7 @@ const CloudProviderEditor = ({ />
)} - {submitError && ( -
- {submitError} -
- )} + {submitError ? : null}