fix(usage): persist dashboard refresh interval (#5057)

This commit is contained in:
makoMakoGo
2026-07-09 10:02:23 +08:00
committed by GitHub
parent 95c917b337
commit 98ccde0050
6 changed files with 209 additions and 7 deletions
+6 -1
View File
@@ -507,7 +507,12 @@ export function SettingsPage({
</TabsContent>
<TabsContent value="usage" className="mt-0">
<UsageDashboard />
<UsageDashboard
refreshIntervalMs={settings?.usageDashboardRefreshIntervalMs}
onRefreshIntervalChange={(usageDashboardRefreshIntervalMs) =>
handleAutoSave({ usageDashboardRefreshIntervalMs })
}
/>
</TabsContent>
</div>
+43 -6
View File
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { UsageHero } from "./UsageHero";
import { UsageTrendChart } from "./UsageTrendChart";
@@ -46,8 +46,17 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
const APP_FILTER_OPTIONS: AppTypeFilter[] = ["all", ...KNOWN_APP_TYPES];
// 0 表示关闭自动刷新(refetchInterval=false
const DEFAULT_REFRESH_INTERVAL_MS = 30000;
const REFRESH_INTERVAL_OPTIONS_MS = [0, 5000, 10000, 30000, 60000] as const;
type RefreshIntervalOption = (typeof REFRESH_INTERVAL_OPTIONS_MS)[number];
const isRefreshIntervalOption = (
value: number | undefined,
): value is RefreshIntervalOption =>
REFRESH_INTERVAL_OPTIONS_MS.includes(value as RefreshIntervalOption);
const normalizeRefreshInterval = (value: number | undefined) =>
isRefreshIntervalOption(value) ? value : DEFAULT_REFRESH_INTERVAL_MS;
// 与 AppSwitcher 的 appIconName 保持一致(codex 复用 openai 图标)
const APP_FILTER_ICON: Record<AppType, string> = {
@@ -64,7 +73,15 @@ const encodeOptionValue = (name: string) => `${DYNAMIC_OPTION_PREFIX}${name}`;
const decodeOptionValue = (value: string) =>
value === "all" ? undefined : value.slice(DYNAMIC_OPTION_PREFIX.length);
export function UsageDashboard() {
interface UsageDashboardProps {
refreshIntervalMs?: number;
onRefreshIntervalChange?: (next: number) => Promise<boolean> | boolean | void;
}
export function UsageDashboard({
refreshIntervalMs: savedRefreshIntervalMs,
onRefreshIntervalChange,
}: UsageDashboardProps = {}) {
const { t, i18n } = useTranslation();
const queryClient = useQueryClient();
const [range, setRange] = useState<UsageRangeSelection>({ preset: "today" });
@@ -73,7 +90,13 @@ export function UsageDashboard() {
undefined,
);
const [model, setModel] = useState<string | undefined>(undefined);
const [refreshIntervalMs, setRefreshIntervalMs] = useState(30000);
const [refreshIntervalMs, setRefreshIntervalMs] = useState(() =>
normalizeRefreshInterval(savedRefreshIntervalMs),
);
useEffect(() => {
setRefreshIntervalMs(normalizeRefreshInterval(savedRefreshIntervalMs));
}, [savedRefreshIntervalMs]);
// 切应用时清掉下游筛选,避免留下一个在新范围内查无数据的"幽灵"组合;
// 切 Provider 同理清掉模型(模型选项随 Provider 级联)。
@@ -95,9 +118,23 @@ export function UsageDashboard() {
// usage 查询,实现实时刷新(仅在 Dashboard 挂载时生效,离开页面自动取消监听)
useUsageEventBridge();
const changeRefreshInterval = (next: number) => {
setRefreshIntervalMs(next);
const changeRefreshInterval = async (next: number) => {
const normalized = normalizeRefreshInterval(next);
const previous = refreshIntervalMs;
setRefreshIntervalMs(normalized);
queryClient.invalidateQueries({ queryKey: usageKeys.all });
try {
const saved = await onRefreshIntervalChange?.(normalized);
if (saved === false) {
setRefreshIntervalMs(previous);
}
} catch (error) {
console.error(
"[UsageDashboard] Failed to persist refresh interval",
error,
);
setRefreshIntervalMs(previous);
}
};
const language = i18n.resolvedLanguage || i18n.language || "en";
+1
View File
@@ -15,6 +15,7 @@ export const settingsSchema = z.object({
skipClaudeOnboarding: z.boolean().optional(),
launchOnStartup: z.boolean().optional(),
enableLocalProxy: z.boolean().optional(),
usageDashboardRefreshIntervalMs: z.number().optional(),
preserveCodexOfficialAuthOnSwitch: z.boolean().optional(),
unifyCodexSessionHistory: z.boolean().optional(),
language: z.enum(["en", "zh", "zh-TW", "ja"]).optional(),
+1
View File
@@ -361,6 +361,7 @@ export interface Settings {
proxyConfirmed?: boolean;
// User has confirmed the usage query first-run notice
usageConfirmed?: boolean;
usageDashboardRefreshIntervalMs?: number;
// User has confirmed the stream check first-run notice
streamCheckConfirmed?: boolean;
// Whether to show the failover toggle independently on the main page