Files
cc-switch/src/hooks/useAutoUsageQuery.ts
Jason 21d29b9c2d feat(usage): add auto-refresh interval for usage queries
New Features:
- Users can configure auto-query interval in "Configure Usage Query" dialog
- Interval in minutes (0 = disabled, recommend 5-60 minutes)
- Auto-query only enabled for currently active provider
- Display last query timestamp in relative time format (e.g., "5 min ago")
- Execute first query immediately when enabled, then repeat at intervals

Technical Implementation:
- Backend: Add auto_query_interval field to UsageScript struct
- Frontend: Create useAutoUsageQuery Hook to manage timers and query state
- UI: Add auto-query interval input field in UsageScriptModal
- Integration: Display auto-query results and timestamp in UsageFooter
- i18n: Add Chinese and English translations

UX Improvements:
- Minimum interval protection (1 minute) to prevent API abuse
- Auto-cleanup timers on component unmount
- Silent failure handling for auto-queries, non-intrusive to users
- Prioritize auto-query results, fallback to manual query results
- Timestamp display positioned next to refresh button for better clarity
2025-11-05 15:48:19 +08:00

119 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from "react";
import { usageApi, type AppId } from "@/lib/api";
import type { Provider, UsageResult } from "@/types";
export interface AutoQueryState {
result: UsageResult | null;
lastQueriedAt: number | null;
isQuerying: boolean;
error: string | null;
}
/**
* 自动用量查询 Hook
* @param provider 供应商对象
* @param appId 应用 IDclaude 或 codex
* @param enabled 是否启用(通常只对当前激活的供应商启用)
* @returns 自动查询状态
*/
export function useAutoUsageQuery(
provider: Provider,
appId: AppId,
enabled: boolean
): AutoQueryState {
const [state, setState] = useState<AutoQueryState>({
result: null,
lastQueriedAt: null,
isQuerying: false,
error: null,
});
const timerRef = useRef<NodeJS.Timeout | null>(null);
const isMountedRef = useRef(true);
// 跟踪组件挂载状态
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
useEffect(() => {
// 清理旧定时器
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
// 重置状态(切换供应商或禁用时)
if (!enabled) {
setState({
result: null,
lastQueriedAt: null,
isQuerying: false,
error: null,
});
return;
}
// 检查是否启用自动查询
const usageScript = provider.meta?.usage_script;
if (!usageScript?.enabled) {
return;
}
const interval = usageScript.autoQueryInterval || 0;
if (interval === 0) {
return; // 间隔为 0不启用自动查询
}
// 限制最小间隔为 1 分钟,避免过于频繁
const actualInterval = Math.max(interval, 1);
// 执行查询的函数
const executeQuery = async () => {
if (!isMountedRef.current) return;
setState((prev) => ({ ...prev, isQuerying: true, error: null }));
try {
const result = await usageApi.query(provider.id, appId);
if (isMountedRef.current) {
setState({
result,
lastQueriedAt: Date.now(),
isQuerying: false,
error: result.success ? null : result.error || "Unknown error",
});
}
} catch (error: any) {
if (isMountedRef.current) {
setState((prev) => ({
...prev,
isQuerying: false,
error: error?.message || "Query failed",
}));
}
console.error("[AutoQuery] Failed:", error);
}
};
// 立即执行一次查询
executeQuery();
// 设置定时器(间隔单位:分钟)
timerRef.current = setInterval(executeQuery, actualInterval * 60 * 1000);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
}, [provider.id, provider.meta?.usage_script, appId, enabled]);
return state;
}