fixed format

This commit is contained in:
Lucas Oliveira 2025-12-03 17:25:54 -03:00
parent cc5711bb5e
commit 89a2d697b3

View file

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