diff --git a/ui/app/routes/api-keys/route.tsx b/ui/app/routes/api-keys/route.tsx index 5324ed74fe..f500c1e7b9 100644 --- a/ui/app/routes/api-keys/route.tsx +++ b/ui/app/routes/api-keys/route.tsx @@ -15,7 +15,7 @@ import { SectionLayout, } from "~/components/layout/PageLayout"; import { logger } from "~/utils/logger"; -import { PageErrorContent } from "~/components/ui/error"; +import { LayoutErrorBoundary, PageErrorContent } from "~/components/ui/error"; import { getPostgresClient, isPostgresAvailable, @@ -314,6 +314,5 @@ export default function ApiKeysPage({ loaderData }: Route.ComponentProps) { } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - logger.error(error); - return ; + return ; } diff --git a/ui/app/routes/autopilot/sessions/$session_id/route.tsx b/ui/app/routes/autopilot/sessions/$session_id/route.tsx index 96d893aecf..91ee2e326d 100644 --- a/ui/app/routes/autopilot/sessions/$session_id/route.tsx +++ b/ui/app/routes/autopilot/sessions/$session_id/route.tsx @@ -10,7 +10,6 @@ import { import { Await, data, - isRouteErrorResponse, useAsyncError, useFetcher, useNavigate, @@ -33,6 +32,7 @@ import { useElementHeight } from "~/hooks/useElementHeight"; import { useInfiniteScrollUp } from "~/hooks/use-infinite-scroll-up"; import type { AutopilotStatus, GatewayEvent } from "~/types/tensorzero"; import { useToast } from "~/hooks/use-toast"; +import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary"; import { SectionErrorNotice } from "~/components/ui/error/ErrorContentPrimitives"; import { getFeatureFlags } from "~/utils/feature_flags"; @@ -810,29 +810,5 @@ export default function AutopilotSessionEventsPage({ } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - logger.error(error); - - if (isRouteErrorResponse(error)) { - return ( -
-

- {error.status} {error.statusText} -

-

{error.data}

-
- ); - } else if (error instanceof Error) { - return ( -
-

Error

-

{error.message}

-
- ); - } else { - return ( -
-

Unknown Error

-
- ); - } + return ; } diff --git a/ui/app/routes/autopilot/sessions/route.tsx b/ui/app/routes/autopilot/sessions/route.tsx index 0319268cfc..195c761444 100644 --- a/ui/app/routes/autopilot/sessions/route.tsx +++ b/ui/app/routes/autopilot/sessions/route.tsx @@ -1,12 +1,7 @@ import { Plus } from "lucide-react"; import { Suspense, use } from "react"; import type { Route } from "./+types/route"; -import { - data, - isRouteErrorResponse, - useLocation, - useNavigate, -} from "react-router"; +import { data, useLocation, useNavigate } from "react-router"; import { useTensorZeroStatusFetcher } from "~/routes/api/tensorzero/status"; import { PageHeader, @@ -16,7 +11,7 @@ import { import { ActionBar } from "~/components/layout/ActionBar"; import { Button } from "~/components/ui/button"; import PageButtons from "~/components/utils/PageButtons"; -import { logger } from "~/utils/logger"; +import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary"; import { SessionsTableRows } from "../AutopilotSessionsTable"; import { getAutopilotClient } from "~/utils/tensorzero.server"; import type { Session } from "~/types/tensorzero"; @@ -214,29 +209,5 @@ export default function AutopilotSessionsPage({ } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - logger.error(error); - - if (isRouteErrorResponse(error)) { - return ( -
-

- {error.status} {error.statusText} -

-

{error.data}

-
- ); - } else if (error instanceof Error) { - return ( -
-

Error

-

{error.message}

-
- ); - } else { - return ( -
-

Unknown Error

-
- ); - } + return ; } diff --git a/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx b/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx index d67fedbb1f..4afdfdcbe8 100644 --- a/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx +++ b/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx @@ -17,13 +17,13 @@ import { getTensorZeroClient } from "~/utils/tensorzero.server"; import { Await, data, - isRouteErrorResponse, Link, redirect, useFetcher, useLocation, type RouteHandle, } from "react-router"; +import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary"; import { Suspense } from "react"; import { InputElement } from "~/components/input_output/InputElement"; import { ChatOutputElement } from "~/components/input_output/ChatOutputElement"; @@ -662,31 +662,7 @@ const MetricRow = ({ }; export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - logger.error(error); - - if (isRouteErrorResponse(error)) { - return ( -
-

- {error.status} {error.statusText} -

-

{error.data}

-
- ); - } else if (error instanceof Error) { - return ( -
-

Error

-

{error.message}

-
- ); - } else { - return ( -
-

Unknown Error

-
- ); - } + return ; } type OutputsSectionProps = { diff --git a/ui/app/routes/observability/functions/route.tsx b/ui/app/routes/observability/functions/route.tsx index 20154bac45..8794f2b336 100644 --- a/ui/app/routes/observability/functions/route.tsx +++ b/ui/app/routes/observability/functions/route.tsx @@ -1,5 +1,4 @@ import type { Route } from "./+types/route"; -import { isRouteErrorResponse } from "react-router"; import FunctionsTable from "./FunctionsTable"; import { useConfig } from "~/context/config"; import { @@ -7,7 +6,7 @@ import { PageLayout, SectionLayout, } from "~/components/layout/PageLayout"; -import { logger } from "~/utils/logger"; +import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary"; import { useMemo, useState } from "react"; import { getTensorZeroClient } from "~/utils/tensorzero.server"; @@ -66,29 +65,5 @@ export default function FunctionsPage({ loaderData }: Route.ComponentProps) { } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - logger.error(error); - - if (isRouteErrorResponse(error)) { - return ( -
-

- {error.status} {error.statusText} -

-

{error.data}

-
- ); - } else if (error instanceof Error) { - return ( -
-

Error

-

{error.message}

-
- ); - } else { - return ( -
-

Unknown Error

-
- ); - } + return ; } diff --git a/ui/app/routes/playground/route.tsx b/ui/app/routes/playground/route.tsx index 1d010a77b7..130f6efe84 100644 --- a/ui/app/routes/playground/route.tsx +++ b/ui/app/routes/playground/route.tsx @@ -4,9 +4,9 @@ import { Link, type RouteHandle, type ShouldRevalidateFunctionArgs, - isRouteErrorResponse, useNavigation, } from "react-router"; +import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary"; import { DatasetCombobox } from "~/components/dataset/DatasetCombobox"; import { FunctionSelector } from "~/components/function/FunctionSelector"; import { PageHeader, PageLayout } from "~/components/layout/PageLayout"; @@ -575,70 +575,7 @@ export default function PlaygroundPage({ loaderData }: Route.ComponentProps) { } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - if (isRouteErrorResponse(error)) { - return ( - -
-
-

- {error.status} {error.statusText} -

-

{error.data}

- - Go to Playground - -
-
-
- ); - } else if (error instanceof Error) { - return ( - -
-
-

Error

-

{error.message}

-
- - Stack trace - -
-                {error.stack}
-              
-
- - Go to Playground - -
-
-
- ); - } else { - return ( - -
-
-

Unknown Error

-

- An unexpected error occurred. Please try again. -

- - Go to Playground - -
-
-
- ); - } + return ; } function GridRow({ diff --git a/ui/app/utils/tensorzero/errors.ts b/ui/app/utils/tensorzero/errors.ts index 26f06c5c00..6a6eee0b40 100644 --- a/ui/app/utils/tensorzero/errors.ts +++ b/ui/app/utils/tensorzero/errors.ts @@ -824,59 +824,77 @@ export interface PageErrorInfo { /** * Returns user-friendly error info for page-level errors. - * Maps status codes to sanitized messages - never exposes raw server strings. + * Uses custom error.data message if provided (string), otherwise falls back + * to generic status-based messages. */ export function getPageErrorInfo(error: unknown): PageErrorInfo { if (isRouteErrorResponse(error)) { + // Use custom message from error.data if it's a string + const customMessage = + typeof error.data === "string" && error.data.length > 0 + ? error.data + : null; + switch (error.status) { case 400: return { title: "Bad Request", message: + customMessage ?? "The request was invalid. Please check your input and try again.", status: 400, }; case 401: return { title: "Unauthorized", - message: "Authentication is required to access this resource.", + message: + customMessage ?? + "Authentication is required to access this resource.", status: 401, }; case 403: return { title: "Forbidden", - message: "You don't have permission to access this resource.", + message: + customMessage ?? + "You don't have permission to access this resource.", status: 403, }; case 404: return { title: "Not Found", - message: "The requested resource could not be found.", + message: + customMessage ?? "The requested resource could not be found.", status: 404, }; case 500: return { title: "Server Error", - message: "The server encountered an error. Please try again later.", + message: + customMessage ?? + "The server encountered an error. Please try again later.", status: 500, }; case 502: return { title: "Bad Gateway", - message: "Unable to reach the server. Please try again later.", + message: + customMessage ?? + "Unable to reach the server. Please try again later.", status: 502, }; case 503: return { title: "Service Unavailable", message: + customMessage ?? "The service is temporarily unavailable. Please try again later.", status: 503, }; default: return { title: "Error", - message: "An unexpected error occurred.", + message: customMessage ?? "An unexpected error occurred.", status: error.status, }; }