diff --git a/frontend/app/api/queries/useProviderHealthQuery.ts b/frontend/app/api/queries/useProviderHealthQuery.ts index 24980467..6586e6dd 100644 --- a/frontend/app/api/queries/useProviderHealthQuery.ts +++ b/frontend/app/api/queries/useProviderHealthQuery.ts @@ -1,153 +1,153 @@ import { - type UseQueryOptions, - useQuery, - useQueryClient, + type UseQueryOptions, + useQuery, + useQueryClient, } from "@tanstack/react-query"; import { useChat } from "@/contexts/chat-context"; import { useGetSettingsQuery } from "./useGetSettingsQuery"; export interface ProviderHealthDetails { - llm_model: string; - embedding_model: string; - endpoint?: string | null; + llm_model: string; + embedding_model: string; + endpoint?: string | null; } export interface ProviderHealthResponse { - status: "healthy" | "unhealthy" | "error" | "backend-unavailable"; - message: string; - provider?: string; - llm_provider?: string; - embedding_provider?: string; - llm_error?: string | null; - embedding_error?: string | null; - details?: ProviderHealthDetails; + status: "healthy" | "unhealthy" | "error" | "backend-unavailable"; + message: string; + provider?: string; + llm_provider?: string; + embedding_provider?: string; + llm_error?: string | null; + embedding_error?: string | null; + details?: ProviderHealthDetails; } export interface ProviderHealthParams { - provider?: "openai" | "ollama" | "watsonx"; - test_completion?: boolean; + provider?: "openai" | "ollama" | "watsonx"; + test_completion?: boolean; } // Track consecutive failures for exponential backoff const failureCountMap = new Map(); export const useProviderHealthQuery = ( - params?: ProviderHealthParams, - options?: Omit< - UseQueryOptions, - "queryKey" | "queryFn" - >, + params?: ProviderHealthParams, + options?: Omit< + UseQueryOptions, + "queryKey" | "queryFn" + >, ) => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient(); - // Get chat error state from context (ChatProvider wraps the entire app in layout.tsx) - const { hasChatError, setChatError } = useChat(); + // Get chat error state from context (ChatProvider wraps the entire app in layout.tsx) + const { hasChatError, setChatError } = useChat(); - const { data: settings = {} } = useGetSettingsQuery(); + const { data: settings = {} } = useGetSettingsQuery(); - async function checkProviderHealth(): Promise { - try { - const url = new URL("/api/provider/health", window.location.origin); + async function checkProviderHealth(): Promise { + try { + const url = new URL("/api/provider/health", window.location.origin); - // Add provider query param if specified - if (params?.provider) { - url.searchParams.set("provider", params.provider); - } + // Add provider query param if specified + if (params?.provider) { + url.searchParams.set("provider", params.provider); + } - // Add test_completion query param if specified or if chat error exists - const testCompletion = params?.test_completion ?? hasChatError; - if (testCompletion) { - url.searchParams.set("test_completion", "true"); - } + // Add test_completion query param if specified or if chat error exists + const testCompletion = params?.test_completion ?? hasChatError; + if (testCompletion) { + url.searchParams.set("test_completion", "true"); + } - const response = await fetch(url.toString()); + const response = await fetch(url.toString()); - if (response.ok) { - return await response.json(); - } else if (response.status === 503) { - // Backend is up but provider validation failed - const errorData = await response.json().catch(() => ({})); - return { - status: "unhealthy", - message: errorData.message || "Provider validation failed", - provider: errorData.provider || params?.provider || "unknown", - llm_provider: errorData.llm_provider, - embedding_provider: errorData.embedding_provider, - llm_error: errorData.llm_error, - embedding_error: errorData.embedding_error, - details: errorData.details, - }; - } else { - // Other backend errors (400, etc.) - treat as provider issues - const errorData = await response.json().catch(() => ({})); - return { - status: "error", - message: errorData.message || "Failed to check provider health", - provider: errorData.provider || params?.provider || "unknown", - llm_provider: errorData.llm_provider, - embedding_provider: errorData.embedding_provider, - llm_error: errorData.llm_error, - embedding_error: errorData.embedding_error, - details: errorData.details, - }; - } - } catch (error) { - // Network error - backend is likely down, don't show provider banner - return { - status: "backend-unavailable", - message: error instanceof Error ? error.message : "Connection failed", - provider: params?.provider || "unknown", - }; - } - } + if (response.ok) { + return await response.json(); + } else if (response.status === 503) { + // Backend is up but provider validation failed + const errorData = await response.json().catch(() => ({})); + return { + status: "unhealthy", + message: errorData.message || "Provider validation failed", + provider: errorData.provider || params?.provider || "unknown", + llm_provider: errorData.llm_provider, + embedding_provider: errorData.embedding_provider, + llm_error: errorData.llm_error, + embedding_error: errorData.embedding_error, + details: errorData.details, + }; + } else { + // Other backend errors (400, etc.) - treat as provider issues + const errorData = await response.json().catch(() => ({})); + return { + status: "error", + message: errorData.message || "Failed to check provider health", + provider: errorData.provider || params?.provider || "unknown", + llm_provider: errorData.llm_provider, + embedding_provider: errorData.embedding_provider, + llm_error: errorData.llm_error, + embedding_error: errorData.embedding_error, + details: errorData.details, + }; + } + } catch (error) { + // Network error - backend is likely down, don't show provider banner + return { + status: "backend-unavailable", + message: error instanceof Error ? error.message : "Connection failed", + provider: params?.provider || "unknown", + }; + } + } - const queryKey = ["provider", "health", params?.test_completion]; - const failureCountKey = queryKey.join("-"); + const queryKey = ["provider", "health", params?.test_completion]; + const failureCountKey = queryKey.join("-"); - const queryResult = useQuery( - { - queryKey, - queryFn: checkProviderHealth, - retry: false, // Don't retry health checks automatically - refetchInterval: (query) => { - const data = query.state.data; - const status = data?.status; + const queryResult = useQuery( + { + queryKey, + queryFn: checkProviderHealth, + retry: false, // Don't retry health checks automatically + refetchInterval: (query) => { + const data = query.state.data; + const status = data?.status; - // If healthy, reset failure count and check every 30 seconds - // Also reset chat error flag if we're using test_completion=true and it succeeded - if (status === "healthy") { - failureCountMap.set(failureCountKey, 0); - // If we were checking with test_completion=true due to chat errors, reset the flag - if (hasChatError && setChatError) { - setChatError(false); - } - return 30000; - } + // If healthy, reset failure count and check every 30 seconds + // Also reset chat error flag if we're using test_completion=true and it succeeded + if (status === "healthy") { + failureCountMap.set(failureCountKey, 0); + // If we were checking with test_completion=true due to chat errors, reset the flag + if (hasChatError && setChatError) { + setChatError(false); + } + return 30000; + } - // If backend unavailable, use moderate polling - if (status === "backend-unavailable") { - return 15000; - } + // If backend unavailable, use moderate polling + if (status === "backend-unavailable") { + return 15000; + } - // For unhealthy/error status, use exponential backoff - const currentFailures = failureCountMap.get(failureCountKey) || 0; - failureCountMap.set(failureCountKey, currentFailures + 1); + // For unhealthy/error status, use exponential backoff + const currentFailures = failureCountMap.get(failureCountKey) || 0; + failureCountMap.set(failureCountKey, currentFailures + 1); - // Exponential backoff: 5s, 10s, 20s, then 30s - const backoffDelays = [5000, 10000, 20000, 30000]; - const delay = - backoffDelays[Math.min(currentFailures, backoffDelays.length - 1)]; + // Exponential backoff: 5s, 10s, 20s, then 30s + const backoffDelays = [5000, 10000, 20000, 30000]; + const delay = + backoffDelays[Math.min(currentFailures, backoffDelays.length - 1)]; - return delay; - }, - refetchOnWindowFocus: false, // Disabled to reduce unnecessary calls on tab switches - refetchOnMount: true, - staleTime: 30000, // Consider data stale after 30 seconds - enabled: !!settings?.edited && options?.enabled !== false, // Only run after onboarding is complete - ...options, - }, - queryClient, - ); + return delay; + }, + refetchOnWindowFocus: false, // Disabled to reduce unnecessary calls on tab switches + refetchOnMount: true, + staleTime: 30000, // Consider data stale after 30 seconds + enabled: !!settings?.edited && options?.enabled !== false, // Only run after onboarding is complete + ...options, + }, + queryClient, + ); - return queryResult; + return queryResult; };