From ee1893060505e18f03d50cdc7b5d2bc5106f7082 Mon Sep 17 00:00:00 2001 From: Ashen <310210685+ashen0x@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:28:18 +0530 Subject: [PATCH] fix: surface backend error details instead of raw axios strings --- ui/app/providers/page.tsx | 13 +++++----- ui/components/detailed-wallet-balance.tsx | 4 ++- .../settings/cli-tokens-settings.tsx | 13 +++------- ui/components/temporary-balances.tsx | 4 ++- ui/lib/api/errors.ts | 26 +++++++++++++++++++ 5 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 ui/lib/api/errors.ts diff --git a/ui/app/providers/page.tsx b/ui/app/providers/page.tsx index 0a40150e..669ac088 100644 --- a/ui/app/providers/page.tsx +++ b/ui/app/providers/page.tsx @@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { getApiErrorMessage } from '@/lib/api/errors'; import { AdminService, ProviderModels, @@ -139,7 +140,7 @@ export default function ProvidersPage() { resetForm(); }, onError: (error: Error) => { - toast.error(`Failed to create provider: ${error.message}`); + toast.error(getApiErrorMessage(error, 'Failed to create provider')); }, }); @@ -153,7 +154,7 @@ export default function ProvidersPage() { toast.success('Provider updated successfully'); }, onError: (error: Error) => { - toast.error(`Failed to update provider: ${error.message}`); + toast.error(getApiErrorMessage(error, 'Failed to update provider')); }, }); @@ -164,7 +165,7 @@ export default function ProvidersPage() { toast.success('Provider deleted successfully'); }, onError: (error: Error) => { - toast.error(`Failed to delete provider: ${error.message}`); + toast.error(getApiErrorMessage(error, 'Failed to delete provider')); }, }); @@ -183,7 +184,7 @@ export default function ProvidersPage() { toast.success('Model deleted successfully'); }, onError: (error: Error) => { - toast.error(`Failed to delete model: ${error.message}`); + toast.error(getApiErrorMessage(error, 'Failed to delete model')); }, }); @@ -205,9 +206,7 @@ export default function ProvidersPage() { toast.success('Account created, but no API key returned.'); } } catch (error: unknown) { - const errorMessage = - error instanceof Error ? error.message : 'Unknown error'; - toast.error(`Failed to create account: ${errorMessage}`); + toast.error(getApiErrorMessage(error, 'Failed to create account')); } finally { setIsCreatingAccount(false); } diff --git a/ui/components/detailed-wallet-balance.tsx b/ui/components/detailed-wallet-balance.tsx index 3400cf9b..794d0a34 100644 --- a/ui/components/detailed-wallet-balance.tsx +++ b/ui/components/detailed-wallet-balance.tsx @@ -4,6 +4,7 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { RefreshCw, AlertCircle, Wallet, User, Coins } from 'lucide-react'; import { WalletService, BalanceDetail } from '@/lib/api/services/wallet'; +import { getApiErrorMessage } from '@/lib/api/errors'; import { Card, CardContent, @@ -191,7 +192,8 @@ export function DetailedWalletBalance({ - Error loading balance: {(error as Error).message} + Error loading balance:{' '} + {getApiErrorMessage(error, 'Request failed')} ) : ( diff --git a/ui/components/settings/cli-tokens-settings.tsx b/ui/components/settings/cli-tokens-settings.tsx index 33921f5d..39e0babe 100644 --- a/ui/components/settings/cli-tokens-settings.tsx +++ b/ui/components/settings/cli-tokens-settings.tsx @@ -21,6 +21,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { AlertCircle, Copy, Trash2, Check } from 'lucide-react'; import { toast } from 'sonner'; +import { getApiErrorMessage } from '@/lib/api/errors'; function formatTs(ts: number | null): string { if (!ts) return '—'; @@ -44,9 +45,7 @@ export function CliTokensSettings(): React.ReactElement { const data = await AdminService.listCliTokens(); setTokens(data); } catch (err: unknown) { - const message = - err instanceof Error ? err.message : 'Failed to load tokens'; - setError(message); + setError(getApiErrorMessage(err, 'Failed to load tokens')); } finally { setLoading(false); } @@ -79,9 +78,7 @@ export function CliTokensSettings(): React.ReactElement { await loadTokens(); toast.success('Token created. Copy it now — it will not be shown again.'); } catch (err: unknown) { - const message = - err instanceof Error ? err.message : 'Failed to create token'; - toast.error(message); + toast.error(getApiErrorMessage(err, 'Failed to create token')); } finally { setCreating(false); } @@ -98,9 +95,7 @@ export function CliTokensSettings(): React.ReactElement { await loadTokens(); toast.success('Token revoked'); } catch (err: unknown) { - const message = - err instanceof Error ? err.message : 'Failed to revoke token'; - toast.error(message); + toast.error(getApiErrorMessage(err, 'Failed to revoke token')); } } diff --git a/ui/components/temporary-balances.tsx b/ui/components/temporary-balances.tsx index f306bcc8..8370ef97 100644 --- a/ui/components/temporary-balances.tsx +++ b/ui/components/temporary-balances.tsx @@ -13,6 +13,7 @@ import { ChevronRight, } from 'lucide-react'; import { AdminService } from '@/lib/api/services/admin'; +import { getApiErrorMessage } from '@/lib/api/errors'; import { Card, CardContent, @@ -169,7 +170,8 @@ export function TemporaryBalances({ - Error loading API keys: {(error as Error).message} + Error loading API keys:{' '} + {getApiErrorMessage(error, 'Request failed')} ) : ( diff --git a/ui/lib/api/errors.ts b/ui/lib/api/errors.ts new file mode 100644 index 00000000..de8e4629 --- /dev/null +++ b/ui/lib/api/errors.ts @@ -0,0 +1,26 @@ +import { isAxiosError } from 'axios'; + +/** + * Human-readable message for a failed API call. Prefers the backend's + * `detail` field; never surfaces raw axios strings like + * "Request failed with status code 500". + */ +export function getApiErrorMessage( + error: unknown, + fallback = 'Something went wrong' +): string { + if (isAxiosError(error)) { + if (!error.response) { + return 'Cannot reach the node. Is it running?'; + } + const detail: unknown = error.response.data?.detail; + if (typeof detail === 'string' && detail) { + return detail; + } + return `${fallback} (HTTP ${error.response.status})`; + } + if (error instanceof Error && error.message) { + return error.message; + } + return fallback; +}