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