feat(usage): add official subscription quota template with unified tier rendering

Changes:
- Add official_subscription template type for Claude/Codex/Gemini
- Replace implicit 'category=official auto-query' with explicit opt-in template
- Default disabled; users enable via usage script modal with configurable interval
- Unify tier→label mapping across subscription and script paths via labeled_tier_parts()
- Fix tray rendering: week aliases (seven_day/opus/sonnet) now use highest utilization
- Add depth guard: official_subscription checks enabled flag in query_provider_usage_inner
- Add cache invalidation symmetry: invalidate_subscription() for disabled providers
- i18n: add templateOfficialSubscription + hint in zh/en/ja/zh-TW

Backend (Rust):
- provider.rs: add TEMPLATE_TYPE_OFFICIAL_SUBSCRIPTION branch, flatten SubscriptionQuota→UsageData
- tray.rs: extract labeled_tier_parts() shared by both summary functions, use max_by for multi-alias groups
- usage_cache.rs: add invalidate_subscription() method
- Test coverage: add week-alias highest-utilization tests for both paths

Frontend (TypeScript):
- UsageScriptModal: add official_subscription to templates, auto-detect for official providers
- ProviderCard: gate useUsageQuery with !isOfficialSubscriptionUsage, pass autoQueryInterval to footer
- SubscriptionQuotaFooter: accept autoQueryInterval prop, default 0 (disabled)
- constants.ts: add TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION

Fixes tier rendering regression where:
- Claude/Codex: seven_day was missed (only weekly_limit matched) → lost 7-day window in tray
- Gemini: gemini_pro/flash/flash_lite fell through to fallback → leaked machine names
- Multi-window (opus+sonnet): find() took first, not worst → underestimated utilization and emoji color

All tests pass (cargo test + cargo clippy clean).
This commit is contained in:
Jason
2026-06-05 19:02:23 +08:00
parent 03a9296c1f
commit 473f21971d
13 changed files with 459 additions and 173 deletions
+8 -1
View File
@@ -9,6 +9,7 @@ interface SubscriptionQuotaFooterProps {
appId: AppId;
inline?: boolean;
isCurrent?: boolean;
autoQueryInterval?: number;
}
interface SubscriptionQuotaViewProps {
@@ -397,12 +398,18 @@ const SubscriptionQuotaFooter: React.FC<SubscriptionQuotaFooterProps> = ({
appId,
inline = false,
isCurrent = false,
autoQueryInterval = 5,
}) => {
const {
data: quota,
isFetching: loading,
refetch,
} = useSubscriptionQuota(appId, isCurrent, isCurrent);
} = useSubscriptionQuota(
appId,
isCurrent,
isCurrent && autoQueryInterval > 0,
autoQueryInterval,
);
if (!isCurrent) return null;
+189 -76
View File
@@ -110,6 +110,9 @@ const generatePresetTemplates = (
// 官方余额查询模板不需要脚本,使用专用 Rust 查询
[TEMPLATE_TYPES.BALANCE]: "",
// 官方订阅额度查询不需要脚本,使用 CLI/OAuth 凭据调用官方 API
[TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION]: "",
});
// 模板名称国际化键映射
@@ -120,6 +123,8 @@ const TEMPLATE_NAME_KEYS: Record<string, string> = {
[TEMPLATE_TYPES.GITHUB_COPILOT]: "usageScript.templateCopilot",
[TEMPLATE_TYPES.TOKEN_PLAN]: "usageScript.templateTokenPlan",
[TEMPLATE_TYPES.BALANCE]: "usageScript.templateBalance",
[TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION]:
"usageScript.templateOfficialSubscription",
};
/** 官方余额查询供应商检测 */
@@ -141,6 +146,45 @@ function detectBalanceProvider(baseUrl: string | undefined): boolean {
return BALANCE_PROVIDERS.some((bp) => bp.pattern.test(baseUrl));
}
function isOfficialSubscriptionProvider(provider: Provider, appId: AppId) {
if (!["claude", "codex", "gemini"].includes(appId)) return false;
if (provider.category === "official") return true;
const config = provider.settingsConfig as Record<string, any>;
if (appId === "claude") {
const baseUrl = config?.env?.ANTHROPIC_BASE_URL;
return !baseUrl || (typeof baseUrl === "string" && baseUrl.trim() === "");
}
if (appId === "codex") {
const apiKey = config?.auth?.OPENAI_API_KEY;
const bearerToken =
typeof config?.config === "string"
? extractCodexExperimentalBearerToken(config.config)
: undefined;
return (
!bearerToken &&
(!apiKey || (typeof apiKey === "string" && apiKey.trim() === ""))
);
}
if (appId === "gemini") {
const env = config?.env || {};
const apiKey = env.GEMINI_API_KEY;
const baseUrl = env.GOOGLE_GEMINI_BASE_URL;
return (
(!apiKey || (typeof apiKey === "string" && apiKey.trim() === "")) &&
(!baseUrl || (typeof baseUrl === "string" && baseUrl.trim() === ""))
);
}
return false;
}
const NATIVE_USAGE_TEMPLATES = new Set<string>([
TEMPLATE_TYPES.GITHUB_COPILOT,
TEMPLATE_TYPES.TOKEN_PLAN,
TEMPLATE_TYPES.BALANCE,
TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION,
]);
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
provider,
appId,
@@ -238,22 +282,33 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
};
const providerCredentials = getProviderCredentials();
const isOfficialSubscription = isOfficialSubscriptionProvider(
provider,
appId,
);
const [script, setScript] = useState<UsageScript>(() => {
const savedScript = provider.meta?.usage_script;
if (savedScript) {
const normalizedScript = createUsageScript(savedScript);
if (
isOfficialSubscription &&
normalizedScript.templateType !== TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION
) {
return createUsageScript();
}
// 已有配置:如果是 coding_plan 但没有 codingPlanProvider,自动检测填充
if (
savedScript.templateType === TEMPLATE_TYPES.TOKEN_PLAN &&
!savedScript.codingPlanProvider
normalizedScript.templateType === TEMPLATE_TYPES.TOKEN_PLAN &&
!normalizedScript.codingPlanProvider
) {
return {
...savedScript,
...normalizedScript,
codingPlanProvider:
detectCodingPlanProvider(providerCredentials.baseUrl) || "kimi",
};
}
return savedScript;
return normalizedScript;
}
const autoDetected = detectCodingPlanProvider(providerCredentials.baseUrl);
@@ -265,6 +320,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
return createUsageScript();
}
if (isOfficialSubscription) {
return createUsageScript();
}
return createUsageScript({
code: PRESET_TEMPLATES[TEMPLATE_TYPES.GENERAL],
});
@@ -327,9 +386,17 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
return TEMPLATE_TYPES.GITHUB_COPILOT;
}
// 优先使用保存的 templateType
if (existingScript?.templateType) {
if (
existingScript?.templateType &&
(!isOfficialSubscription ||
existingScript.templateType === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION)
) {
return existingScript.templateType as string;
}
// 官方 CLI/OAuth 供应商默认使用官方订阅额度模板,但开关默认关闭
if (isOfficialSubscription) {
return TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION;
}
// 向后兼容:根据字段推断模板类型
// 检测 NEW_API 模板(有 accessToken 或 userId
if (existingScript?.accessToken || existingScript?.userId) {
@@ -378,12 +445,8 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
};
const handleSave = () => {
// Copilot、Coding Plan、Balance 模板不需要脚本验证
if (
selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT &&
selectedTemplate !== TEMPLATE_TYPES.TOKEN_PLAN &&
selectedTemplate !== TEMPLATE_TYPES.BALANCE
) {
// 专用模板不需要脚本验证
if (!NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "")) {
if (script.enabled && !script.code.trim()) {
toast.error(t("usageScript.scriptEmpty"));
return;
@@ -403,6 +466,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
| "github_copilot"
| "token_plan"
| "balance"
| "official_subscription"
| undefined,
};
onSave(scriptWithTemplate);
@@ -412,6 +476,28 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
const handleTest = async () => {
setTesting(true);
try {
// 官方订阅额度模板使用 CLI/OAuth 凭据和官方 API
if (selectedTemplate === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION) {
const { subscriptionApi } = await import("@/lib/api/subscription");
const quota = await subscriptionApi.getQuota(appId);
if (quota.success && quota.tiers.length > 0) {
const summary = quota.tiers
.map((tier) => `${tier.name}: ${Math.round(tier.utilization)}%`)
.join(", ");
toast.success(`${t("usageScript.testSuccess")}${summary}`, {
duration: 3000,
closeButton: true,
});
queryClient.setQueryData(["subscription", "quota", appId], quota);
} else {
toast.error(
`${t("usageScript.testFailed")}: ${quota.error || t("endpointTest.noResult")}`,
{ duration: 5000 },
);
}
return;
}
// 官方余额查询模板使用专用 API
if (selectedTemplate === TEMPLATE_TYPES.BALANCE) {
const baseUrl = providerCredentials.baseUrl ?? "";
@@ -654,6 +740,16 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
accessToken: undefined,
userId: undefined,
});
} else if (presetName === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION) {
// 官方订阅额度查询不需要脚本,使用 CLI/OAuth 凭据
setScript({
...script,
code: "",
apiKey: undefined,
baseUrl: undefined,
accessToken: undefined,
userId: undefined,
});
}
setSelectedTemplate(presetName);
}
@@ -681,7 +777,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
variant="outline"
size="sm"
onClick={handleFormat}
disabled={!script.enabled}
disabled={
!script.enabled ||
NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "")
}
title={t("usageScript.format")}
>
<Wand2 size={14} className="mr-1" />
@@ -742,8 +841,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
if (isCopilotProvider) {
return name === TEMPLATE_TYPES.GITHUB_COPILOT;
}
// 官方 CLI/OAuth 供应商只显示官方订阅额度模板
if (isOfficialSubscription) {
return name === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION;
}
// 非 Copilot 供应商不显示 copilot 模板
return name !== TEMPLATE_TYPES.GITHUB_COPILOT;
return (
name !== TEMPLATE_TYPES.GITHUB_COPILOT &&
name !== TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION
);
})
.map((name) => {
const isSelected = selectedTemplate === name;
@@ -865,6 +971,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
</div>
)}
{/* 官方订阅额度模式:自动提示 */}
{selectedTemplate === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION && (
<div className="space-y-2 border-t border-white/10 pt-3">
<p className="text-sm text-muted-foreground">
{t("usageScript.officialSubscriptionHint")}
</p>
</div>
)}
{/* Coding Plan 模式:供应商选择 */}
{selectedTemplate === TEMPLATE_TYPES.TOKEN_PLAN && (
<div className="space-y-3 border-t border-white/10 pt-3">
@@ -1191,43 +1306,41 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
</div>
</div>
{/* 提取器代码 - Copilot 模板不需要 */}
{selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT &&
selectedTemplate !== TEMPLATE_TYPES.TOKEN_PLAN && (
<div className="space-y-4 glass rounded-xl border border-white/10 p-6">
<div className="flex items-center justify-between">
<Label className="text-base font-medium">
{t("usageScript.extractorCode")}
</Label>
<div className="text-xs text-muted-foreground">
{t("usageScript.extractorHint")}
</div>
{/* 提取器代码 - 专用模板不需要 */}
{!NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "") && (
<div className="space-y-4 glass rounded-xl border border-white/10 p-6">
<div className="flex items-center justify-between">
<Label className="text-base font-medium">
{t("usageScript.extractorCode")}
</Label>
<div className="text-xs text-muted-foreground">
{t("usageScript.extractorHint")}
</div>
<JsonEditor
id="usage-code"
value={script.code || ""}
onChange={(value) =>
setScript((prev) => ({ ...prev, code: value }))
}
height={480}
language="javascript"
showMinimap={false}
/>
</div>
)}
<JsonEditor
id="usage-code"
value={script.code || ""}
onChange={(value) =>
setScript((prev) => ({ ...prev, code: value }))
}
height={480}
language="javascript"
showMinimap={false}
/>
</div>
)}
{/* 帮助信息 - Copilot 模板不需要 */}
{selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT &&
selectedTemplate !== TEMPLATE_TYPES.TOKEN_PLAN && (
<div className="glass rounded-xl border border-white/10 p-6 text-sm text-foreground/90">
<h4 className="font-medium mb-2">
{t("usageScript.scriptHelp")}
</h4>
<div className="space-y-3 text-xs">
<div>
<strong>{t("usageScript.configFormat")}</strong>
<pre className="mt-1 p-2 bg-black/20 text-foreground rounded border border-white/10 text-[10px] overflow-x-auto">
{`({
{/* 帮助信息 - 专用模板不需要 */}
{!NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "") && (
<div className="glass rounded-xl border border-white/10 p-6 text-sm text-foreground/90">
<h4 className="font-medium mb-2">
{t("usageScript.scriptHelp")}
</h4>
<div className="space-y-3 text-xs">
<div>
<strong>{t("usageScript.configFormat")}</strong>
<pre className="mt-1 p-2 bg-black/20 text-foreground rounded border border-white/10 text-[10px] overflow-x-auto">
{`({
request: {
url: "{{baseUrl}}/api/usage",
method: "POST",
@@ -1244,39 +1357,39 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
};
}
})`}
</pre>
</div>
</pre>
</div>
<div>
<strong>{t("usageScript.extractorFormat")}</strong>
<ul className="mt-1 space-y-0.5 ml-2">
<li>{t("usageScript.fieldIsValid")}</li>
<li>{t("usageScript.fieldInvalidMessage")}</li>
<li>{t("usageScript.fieldRemaining")}</li>
<li>{t("usageScript.fieldUnit")}</li>
<li>{t("usageScript.fieldPlanName")}</li>
<li>{t("usageScript.fieldTotal")}</li>
<li>{t("usageScript.fieldUsed")}</li>
<li>{t("usageScript.fieldExtra")}</li>
</ul>
</div>
<div>
<strong>{t("usageScript.extractorFormat")}</strong>
<ul className="mt-1 space-y-0.5 ml-2">
<li>{t("usageScript.fieldIsValid")}</li>
<li>{t("usageScript.fieldInvalidMessage")}</li>
<li>{t("usageScript.fieldRemaining")}</li>
<li>{t("usageScript.fieldUnit")}</li>
<li>{t("usageScript.fieldPlanName")}</li>
<li>{t("usageScript.fieldTotal")}</li>
<li>{t("usageScript.fieldUsed")}</li>
<li>{t("usageScript.fieldExtra")}</li>
</ul>
</div>
<div className="text-muted-foreground">
<strong>{t("usageScript.tips")}</strong>
<ul className="mt-1 space-y-0.5 ml-2">
<li>
{t("usageScript.tip1", {
apiKey: "{{apiKey}}",
baseUrl: "{{baseUrl}}",
})}
</li>
<li>{t("usageScript.tip2")}</li>
<li>{t("usageScript.tip3")}</li>
</ul>
</div>
<div className="text-muted-foreground">
<strong>{t("usageScript.tips")}</strong>
<ul className="mt-1 space-y-0.5 ml-2">
<li>
{t("usageScript.tip1", {
apiKey: "{{apiKey}}",
baseUrl: "{{baseUrl}}",
})}
</li>
<li>{t("usageScript.tip2")}</li>
<li>{t("usageScript.tip3")}</li>
</ul>
</div>
</div>
)}
</div>
)}
</div>
)}
+22 -8
View File
@@ -14,7 +14,7 @@ import UsageFooter from "@/components/UsageFooter";
import SubscriptionQuotaFooter from "@/components/SubscriptionQuotaFooter";
import CopilotQuotaFooter from "@/components/CopilotQuotaFooter";
import CodexOauthQuotaFooter from "@/components/CodexOauthQuotaFooter";
import { PROVIDER_TYPES } from "@/config/constants";
import { PROVIDER_TYPES, TEMPLATE_TYPES } from "@/config/constants";
import { isHermesReadOnlyProvider } from "@/config/hermesProviderPresets";
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
import { FailoverPriorityBadge } from "@/components/providers/FailoverPriorityBadge";
@@ -192,6 +192,13 @@ export function ProviderCard({
const usageEnabled = provider.meta?.usage_script?.enabled ?? false;
const isOfficial = isOfficialProvider(provider, appId);
const supportsOfficialSubscription =
isOfficial && ["claude", "codex", "gemini"].includes(appId);
const isOfficialSubscriptionUsage =
provider.meta?.usage_script?.templateType ===
TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION;
const officialSubscriptionEnabled =
supportsOfficialSubscription && usageEnabled && isOfficialSubscriptionUsage;
const isOfficialBlockedByProxy =
isProxyTakeover && (provider.category === "official" || isOfficial);
const isCopilot =
@@ -231,7 +238,7 @@ export function ProviderCard({
: 0;
const { data: usage } = useUsageQuery(provider.id, appId, {
enabled: usageEnabled,
enabled: usageEnabled && !isOfficial && !isOfficialSubscriptionUsage,
autoQueryInterval,
});
@@ -469,11 +476,16 @@ export function ProviderCard({
isCurrent={isCurrent}
/>
) : isOfficial ? (
<SubscriptionQuotaFooter
appId={appId}
inline={true}
isCurrent={isCurrent}
/>
officialSubscriptionEnabled ? (
<SubscriptionQuotaFooter
appId={appId}
inline={true}
isCurrent={isCurrent}
autoQueryInterval={
provider.meta?.usage_script?.autoQueryInterval ?? 0
}
/>
) : null
) : hasMultiplePlans ? (
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
<span className="font-medium">
@@ -540,7 +552,9 @@ export function ProviderCard({
: undefined
}
onConfigureUsage={
isOfficial || isCopilot || isCodexOauth
(isOfficial && !supportsOfficialSubscription) ||
isCopilot ||
isCodexOauth
? undefined
: () => onConfigureUsage(provider)
}
+1
View File
@@ -12,6 +12,7 @@ export const TEMPLATE_TYPES = {
GITHUB_COPILOT: "github_copilot",
TOKEN_PLAN: "token_plan",
BALANCE: "balance",
OFFICIAL_SUBSCRIPTION: "official_subscription",
} as const;
export type TemplateType = (typeof TEMPLATE_TYPES)[keyof typeof TEMPLATE_TYPES];
+3
View File
@@ -311,6 +311,9 @@ export function useProviderActions(
await queryClient.invalidateQueries({
queryKey: ["usage", provider.id, activeApp],
});
await queryClient.invalidateQueries({
queryKey: ["subscription", "quota", activeApp],
});
toast.success(
t("provider.usageSaved", {
defaultValue: "用量查询配置已保存",
+2
View File
@@ -1488,9 +1488,11 @@
"templateCopilot": "GitHub Copilot",
"templateTokenPlan": "Token Plan",
"templateBalance": "Official",
"templateOfficialSubscription": "Official Subscription",
"copilotAutoAuth": "Auto OAuth authentication, no manual credentials needed",
"tokenPlanHint": "Automatically uses the provider's API Key and Base URL to query Token Plan quota",
"balanceHint": "Automatically uses the provider's API Key to query account balance",
"officialSubscriptionHint": "Reads the local CLI OAuth credentials and calls the official API to query subscription quota. Disabled by default and only requests after you enable it.",
"resetDate": "Reset date",
"premiumRequests": "Premium Requests",
"credentialsConfig": "Credentials",
+2
View File
@@ -1488,9 +1488,11 @@
"templateCopilot": "GitHub Copilot",
"templateTokenPlan": "Token Plan",
"templateBalance": "公式",
"templateOfficialSubscription": "公式サブスクリプション",
"copilotAutoAuth": "OAuth 認証を自動使用、手動設定不要",
"tokenPlanHint": "プロバイダーのAPI KeyとBase URLを使用してToken Planクォータを自動クエリ",
"balanceHint": "プロバイダーのAPI Keyを使用してアカウント残高を自動クエリ",
"officialSubscriptionHint": "ローカル CLI の OAuth 認証情報を読み取り、公式 API でサブスクリプション枠を照会します。既定では無効で、有効化した後のみリクエストします。",
"resetDate": "リセット日",
"premiumRequests": "Premium リクエスト",
"credentialsConfig": "認証情報",
+2
View File
@@ -1434,9 +1434,11 @@
"templateCopilot": "GitHub Copilot",
"templateTokenPlan": "Token Plan",
"templateBalance": "官方",
"templateOfficialSubscription": "官方訂閱",
"copilotAutoAuth": "自動使用 OAuth 驗證,無需手動設定憑證",
"tokenPlanHint": "自動使用供應商的 API Key 和 Base URL 查詢 Token Plan 額度",
"balanceHint": "自動使用供應商的 API Key 查詢帳號餘額",
"officialSubscriptionHint": "讀取本機 CLI 的 OAuth 憑證,並呼叫官方介面查詢訂閱額度。預設關閉,只有啟用後才會請求。",
"resetDate": "重設日期",
"premiumRequests": "Premium 請求",
"credentialsConfig": "憑證設定",
+2
View File
@@ -1488,9 +1488,11 @@
"templateCopilot": "GitHub Copilot",
"templateTokenPlan": "Token Plan",
"templateBalance": "官方",
"templateOfficialSubscription": "官方订阅",
"copilotAutoAuth": "自动使用 OAuth 认证,无需手动配置凭证",
"tokenPlanHint": "自动使用供应商的 API Key 和 Base URL 查询 Token Plan 额度",
"balanceHint": "自动使用供应商的 API Key 查询账户余额",
"officialSubscriptionHint": "读取本机 CLI 的 OAuth 凭据,并调用官方接口查询订阅额度。默认关闭,只有启用后才会请求。",
"resetDate": "重置日期",
"premiumRequests": "Premium 请求",
"credentialsConfig": "凭证配置",
+13 -4
View File
@@ -16,15 +16,24 @@ export function useSubscriptionQuota(
appId: AppId,
enabled: boolean,
autoQuery = false,
autoQueryIntervalMinutes = 5,
) {
const refetchInterval =
autoQuery && autoQueryIntervalMinutes > 0
? Math.max(autoQueryIntervalMinutes, 1) * 60 * 1000
: false;
return useQuery({
queryKey: subscriptionKeys.quota(appId),
queryFn: () => subscriptionApi.getQuota(appId),
enabled: enabled && ["claude", "codex", "gemini"].includes(appId),
refetchInterval: autoQuery ? REFETCH_INTERVAL : false,
refetchIntervalInBackground: autoQuery,
refetchOnWindowFocus: autoQuery,
staleTime: REFETCH_INTERVAL,
refetchInterval,
refetchIntervalInBackground: Boolean(refetchInterval),
refetchOnWindowFocus: Boolean(refetchInterval),
staleTime:
autoQueryIntervalMinutes > 0
? Math.max(autoQueryIntervalMinutes, 1) * 60 * 1000
: REFETCH_INTERVAL,
retry: 1,
});
}