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
119 lines
2.9 KiB
TypeScript
119 lines
2.9 KiB
TypeScript
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 应用 ID(claude 或 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;
|
||
}
|