From 266a5701c510a2e63da8b1fb72c66936b084d90f Mon Sep 17 00:00:00 2001 From: aqilaziz Date: Thu, 21 May 2026 11:44:57 +0700 Subject: [PATCH 1/4] fix(ai): contain provider setup errors --- .../components/settings/panels/AIPanel.tsx | 75 +++++++++++++++---- .../panels/__tests__/AIPanel.test.tsx | 37 ++++++++- 2 files changed, 96 insertions(+), 16 deletions(-) diff --git a/app/src/components/settings/panels/AIPanel.tsx b/app/src/components/settings/panels/AIPanel.tsx index 05fb6d91a6..e33dfccd34 100644 --- a/app/src/components/settings/panels/AIPanel.tsx +++ b/app/src/components/settings/panels/AIPanel.tsx @@ -200,6 +200,37 @@ const EMPTY_ROUTING: RoutingMap = { const EMPTY_SETTINGS: AISettings = { cloudProviders: [], routing: EMPTY_ROUTING }; +type ProviderSetupError = { summary: string; detail?: string }; + +const MAX_PROVIDER_SETUP_DETAIL_LENGTH = 1000; + +function formatProviderSetupError(err: unknown): ProviderSetupError { + const raw = err instanceof Error ? err.message : String(err ?? ''); + const fallback = 'Provider setup failed. Check the provider settings and try again.'; + const trimmedRaw = raw.trim(); + if (!trimmedRaw) return { summary: fallback }; + + const withoutUrls = trimmedRaw.replace(/https?:\/\/[^\s"]+/g, ''); + const jsonStart = withoutUrls.search(/\s(?:\[|\{)/); + const clipped = jsonStart === -1 ? withoutUrls : withoutUrls.slice(0, jsonStart); + let summary = clipped + .replace(/^Error:\s*/i, '') + .replace(/^Could not reach ([^:]+):\s*/i, 'Could not reach $1. ') + .trim(); + + if (!summary) summary = fallback; + if (summary.length > 220) { + summary = `${summary.slice(0, 217).trimEnd()}...`; + } + + const clippedDetail = + trimmedRaw.length > MAX_PROVIDER_SETUP_DETAIL_LENGTH + ? `${trimmedRaw.slice(0, MAX_PROVIDER_SETUP_DETAIL_LENGTH - 3).trimEnd()}...` + : trimmedRaw; + const detail = clippedDetail !== summary ? clippedDetail : undefined; + return { summary, detail }; +} + function maskKeyLabel(hasKey: boolean): string { return hasKey ? '•••• configured' : 'Not configured'; } @@ -541,7 +572,7 @@ const ProviderKeyDialog = ({ const { t } = useT(); const [value, setValue] = useState(isLocalRuntime ? defaultEndpointFor(slug) : ''); const [phase, setPhase] = useState<'idle' | 'saving'>('idle'); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const busy = phase !== 'idle'; const placeholder = isLocalRuntime @@ -564,11 +595,13 @@ const ProviderKeyDialog = ({ const handleSave = async () => { const trimmed = value.trim(); if (!trimmed) { - setError(isLocalRuntime ? 'Endpoint URL is required' : t('settings.ai.apiKeyRequired')); + setError({ + summary: isLocalRuntime ? 'Endpoint URL is required' : t('settings.ai.apiKeyRequired'), + }); return; } if (isLocalRuntime && !/^https?:\/\//i.test(trimmed)) { - setError('Endpoint must start with http:// or https://'); + setError({ summary: 'Endpoint must start with http:// or https://' }); return; } setError(null); @@ -577,7 +610,7 @@ const ProviderKeyDialog = ({ try { await onSubmit(trimmed); } catch (err) { - setError(err instanceof Error ? err.message : String(err)); + setError(formatProviderSetupError(err)); setPhase('idle'); } }; @@ -619,9 +652,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}
@@ -646,6 +677,26 @@ const ProviderKeyDialog = ({ }; // ───────────────────────────────────────────────────────────────────────────── +function ProviderSetupErrorMessage({ error }: { error: ProviderSetupError }) { + return ( +
+

+ {error.summary} +

+ {error.detail && ( +
+ + Details + +
+            {error.detail}
+          
+
+ )} +
+ ); +} + // Background loop controls + usage diagnostics // ───────────────────────────────────────────────────────────────────────────── @@ -2529,7 +2580,7 @@ const CloudProviderEditor = ({ const [endpoint, setEndpoint] = useState(initial?.endpoint ?? defaultEndpointFor(defaultSlug)); const [apiKey, setApiKey] = useState(''); const [saving, setSaving] = useState(false); - const [submitError, setSubmitError] = useState(null); + const [submitError, setSubmitError] = useState(null); const isOpenHuman = slug === 'openhuman'; const hasExistingKey = (initial?.maskedKey ?? '').startsWith('••••'); @@ -2617,11 +2668,7 @@ const CloudProviderEditor = ({ />
)} - {submitError && ( -
- {submitError} -
- )} + {submitError && }