Move canFetch guard before first await so disabled refresh bails out synchronously instead of entering async path. Add .catch() swallow for fire-and-forget call to prevent unhandled promise rejections when react-query cancels pending queries during rapid tooltip open/close cycles.
104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
import { useCallback, useMemo } from "react";
|
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
|
import { useSessionStore } from "@/stores/session-store";
|
|
import { providerUsageCopy } from "./copy";
|
|
import type { ProviderUsageListPayload, ProviderUsageView } from "./types";
|
|
|
|
export const PROVIDER_USAGE_STALE_TIME_MS = 5 * 60 * 1000;
|
|
|
|
type ProviderUsageClient = Pick<DaemonClient, "listProviderUsage">;
|
|
|
|
export function providerUsageQueryKey(serverId: string | null | undefined) {
|
|
return ["providerUsage", serverId ?? ""] as const;
|
|
}
|
|
|
|
async function fetchProviderUsage(client: ProviderUsageClient): Promise<ProviderUsageListPayload> {
|
|
return client.listProviderUsage();
|
|
}
|
|
|
|
interface UseProviderUsageOptions {
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export function useProviderUsage(
|
|
serverId: string | null | undefined,
|
|
options: UseProviderUsageOptions = {},
|
|
): {
|
|
view: ProviderUsageView;
|
|
refresh: () => Promise<void>;
|
|
canFetch: boolean;
|
|
} {
|
|
const queryClient = useQueryClient();
|
|
const client = useHostRuntimeClient(serverId ?? "");
|
|
const isConnected = useHostRuntimeIsConnected(serverId ?? "");
|
|
const supportsProviderUsage = useSessionStore(
|
|
(state) => state.sessions[serverId ?? ""]?.serverInfo?.features?.providerUsageList === true,
|
|
);
|
|
const queryKey = useMemo(() => providerUsageQueryKey(serverId), [serverId]);
|
|
const canFetch = Boolean(serverId && client && isConnected && supportsProviderUsage);
|
|
const enabled = Boolean((options.enabled ?? true) && canFetch);
|
|
|
|
const queryFn = useCallback(async () => {
|
|
if (!client) {
|
|
throw new Error(providerUsageCopy.clientUnavailable);
|
|
}
|
|
return fetchProviderUsage(client);
|
|
}, [client]);
|
|
|
|
const query = useQuery({
|
|
queryKey,
|
|
queryFn,
|
|
enabled,
|
|
staleTime: PROVIDER_USAGE_STALE_TIME_MS,
|
|
refetchOnMount: true,
|
|
refetchOnReconnect: false,
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
|
|
const refresh = useCallback(async () => {
|
|
if (!canFetch) return;
|
|
await queryClient.invalidateQueries({ queryKey });
|
|
await queryClient.fetchQuery({
|
|
queryKey,
|
|
queryFn,
|
|
staleTime: PROVIDER_USAGE_STALE_TIME_MS,
|
|
});
|
|
}, [canFetch, queryClient, queryFn, queryKey]);
|
|
|
|
const view = useMemo<ProviderUsageView>(() => {
|
|
if (!serverId || !client || !isConnected) {
|
|
return { kind: "error", message: providerUsageCopy.hostUnavailable };
|
|
}
|
|
if (!supportsProviderUsage) {
|
|
return { kind: "error", message: providerUsageCopy.hostUpgradeRequired };
|
|
}
|
|
if (query.data) {
|
|
return {
|
|
kind: "ready",
|
|
payload: query.data,
|
|
isRefreshing: query.isFetching,
|
|
};
|
|
}
|
|
if (query.isError) {
|
|
return {
|
|
kind: "error",
|
|
message: query.error instanceof Error ? query.error.message : String(query.error),
|
|
};
|
|
}
|
|
return { kind: "loading" };
|
|
}, [
|
|
client,
|
|
isConnected,
|
|
query.data,
|
|
query.error,
|
|
query.isError,
|
|
query.isFetching,
|
|
serverId,
|
|
supportsProviderUsage,
|
|
]);
|
|
|
|
return { view, refresh, canFetch };
|
|
}
|