mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 03:00:49 +08:00
feat(quota): add zustand store for quota state caching
This commit is contained in:
@@ -3,9 +3,17 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { EmptyState } from '@/components/ui/EmptyState';
|
import { EmptyState } from '@/components/ui/EmptyState';
|
||||||
import { useAuthStore, useThemeStore } from '@/stores';
|
import { useAuthStore, useQuotaStore, useThemeStore } from '@/stores';
|
||||||
import { apiCallApi, authFilesApi, getApiCallErrorMessage } from '@/services/api';
|
import { apiCallApi, authFilesApi, getApiCallErrorMessage } from '@/services/api';
|
||||||
import type { AuthFileItem } from '@/types';
|
import type {
|
||||||
|
AntigravityQuotaGroup,
|
||||||
|
AntigravityQuotaState,
|
||||||
|
AuthFileItem,
|
||||||
|
CodexQuotaState,
|
||||||
|
CodexQuotaWindow,
|
||||||
|
GeminiCliQuotaBucketState,
|
||||||
|
GeminiCliQuotaState
|
||||||
|
} from '@/types';
|
||||||
import styles from './QuotaPage.module.scss';
|
import styles from './QuotaPage.module.scss';
|
||||||
|
|
||||||
type ThemeColors = { bg: string; text: string; border?: string };
|
type ThemeColors = { bg: string; text: string; border?: string };
|
||||||
@@ -56,21 +64,6 @@ const TYPE_COLORS: Record<string, TypeColorSet> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AntigravityQuotaGroup {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
models: string[];
|
|
||||||
remainingFraction: number;
|
|
||||||
resetTime?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AntigravityQuotaState {
|
|
||||||
status: 'idle' | 'loading' | 'success' | 'error';
|
|
||||||
groups: AntigravityQuotaGroup[];
|
|
||||||
error?: string;
|
|
||||||
errorStatus?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeminiCliQuotaBucket {
|
interface GeminiCliQuotaBucket {
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
model_id?: string;
|
model_id?: string;
|
||||||
@@ -88,22 +81,6 @@ interface GeminiCliQuotaPayload {
|
|||||||
buckets?: GeminiCliQuotaBucket[];
|
buckets?: GeminiCliQuotaBucket[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GeminiCliQuotaBucketState {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
remainingFraction: number | null;
|
|
||||||
remainingAmount: number | null;
|
|
||||||
resetTime: string | undefined;
|
|
||||||
tokenType: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeminiCliQuotaState {
|
|
||||||
status: 'idle' | 'loading' | 'success' | 'error';
|
|
||||||
buckets: GeminiCliQuotaBucketState[];
|
|
||||||
error?: string;
|
|
||||||
errorStatus?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AntigravityQuotaInfo {
|
interface AntigravityQuotaInfo {
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
quotaInfo?: {
|
quotaInfo?: {
|
||||||
@@ -199,6 +176,7 @@ interface CodexUsageWindow {
|
|||||||
interface CodexRateLimitInfo {
|
interface CodexRateLimitInfo {
|
||||||
allowed?: boolean;
|
allowed?: boolean;
|
||||||
limit_reached?: boolean;
|
limit_reached?: boolean;
|
||||||
|
limitReached?: boolean;
|
||||||
primary_window?: CodexUsageWindow | null;
|
primary_window?: CodexUsageWindow | null;
|
||||||
primaryWindow?: CodexUsageWindow | null;
|
primaryWindow?: CodexUsageWindow | null;
|
||||||
secondary_window?: CodexUsageWindow | null;
|
secondary_window?: CodexUsageWindow | null;
|
||||||
@@ -214,21 +192,6 @@ interface CodexUsagePayload {
|
|||||||
codeReviewRateLimit?: CodexRateLimitInfo | null;
|
codeReviewRateLimit?: CodexRateLimitInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CodexQuotaWindow {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
usedPercent: number | null;
|
|
||||||
resetLabel: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CodexQuotaState {
|
|
||||||
status: 'idle' | 'loading' | 'success' | 'error';
|
|
||||||
windows: CodexQuotaWindow[];
|
|
||||||
planType?: string | null;
|
|
||||||
error?: string;
|
|
||||||
errorStatus?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CODEX_USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage';
|
const CODEX_USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage';
|
||||||
|
|
||||||
const CODEX_REQUEST_HEADERS = {
|
const CODEX_REQUEST_HEADERS = {
|
||||||
@@ -293,6 +256,20 @@ function normalizeNumberValue(value: unknown): number | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeQuotaFraction(value: unknown): number | null {
|
||||||
|
const normalized = normalizeNumberValue(value);
|
||||||
|
if (normalized !== null) return normalized;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
if (trimmed.endsWith('%')) {
|
||||||
|
const parsed = Number(trimmed.slice(0, -1));
|
||||||
|
return Number.isFinite(parsed) ? parsed / 100 : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizePlanType(value: unknown): string | null {
|
function normalizePlanType(value: unknown): string | null {
|
||||||
const normalized = normalizeStringValue(value);
|
const normalized = normalizeStringValue(value);
|
||||||
return normalized ? normalized.toLowerCase() : null;
|
return normalized ? normalized.toLowerCase() : null;
|
||||||
@@ -506,13 +483,13 @@ function getAntigravityQuotaInfo(entry?: AntigravityQuotaInfo): {
|
|||||||
const quotaInfo = entry.quotaInfo ?? entry.quota_info ?? {};
|
const quotaInfo = entry.quotaInfo ?? entry.quota_info ?? {};
|
||||||
const remainingValue =
|
const remainingValue =
|
||||||
quotaInfo.remainingFraction ?? quotaInfo.remaining_fraction ?? quotaInfo.remaining;
|
quotaInfo.remainingFraction ?? quotaInfo.remaining_fraction ?? quotaInfo.remaining;
|
||||||
const remainingFraction = Number(remainingValue);
|
const remainingFraction = normalizeQuotaFraction(remainingValue);
|
||||||
const resetValue = quotaInfo.resetTime ?? quotaInfo.reset_time;
|
const resetValue = quotaInfo.resetTime ?? quotaInfo.reset_time;
|
||||||
const resetTime = typeof resetValue === 'string' ? resetValue : undefined;
|
const resetTime = typeof resetValue === 'string' ? resetValue : undefined;
|
||||||
const displayName = typeof entry.displayName === 'string' ? entry.displayName : undefined;
|
const displayName = typeof entry.displayName === 'string' ? entry.displayName : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
remainingFraction: Number.isFinite(remainingFraction) ? remainingFraction : null,
|
remainingFraction,
|
||||||
resetTime,
|
resetTime,
|
||||||
displayName
|
displayName
|
||||||
};
|
};
|
||||||
@@ -554,10 +531,12 @@ function buildAntigravityQuotaGroups(models: AntigravityModelsPayload): Antigrav
|
|||||||
const quotaEntries = matches
|
const quotaEntries = matches
|
||||||
.map(({ id, entry }) => {
|
.map(({ id, entry }) => {
|
||||||
const info = getAntigravityQuotaInfo(entry);
|
const info = getAntigravityQuotaInfo(entry);
|
||||||
if (info.remainingFraction === null) return null;
|
const remainingFraction =
|
||||||
|
info.remainingFraction ?? (info.resetTime ? 0 : null);
|
||||||
|
if (remainingFraction === null) return null;
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
remainingFraction: info.remainingFraction,
|
remainingFraction,
|
||||||
resetTime: info.resetTime,
|
resetTime: info.resetTime,
|
||||||
displayName: info.displayName
|
displayName: info.displayName
|
||||||
};
|
};
|
||||||
@@ -683,22 +662,24 @@ export function QuotaPage() {
|
|||||||
const [codexPageSize, setCodexPageSize] = useState(6);
|
const [codexPageSize, setCodexPageSize] = useState(6);
|
||||||
const [geminiCliPage, setGeminiCliPage] = useState(1);
|
const [geminiCliPage, setGeminiCliPage] = useState(1);
|
||||||
const [geminiCliPageSize, setGeminiCliPageSize] = useState(6);
|
const [geminiCliPageSize, setGeminiCliPageSize] = useState(6);
|
||||||
const [antigravityQuota, setAntigravityQuota] = useState<Record<string, AntigravityQuotaState>>(
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
const [antigravityLoading, setAntigravityLoading] = useState(false);
|
const [antigravityLoading, setAntigravityLoading] = useState(false);
|
||||||
const [antigravityLoadingScope, setAntigravityLoadingScope] = useState<
|
const [antigravityLoadingScope, setAntigravityLoadingScope] = useState<
|
||||||
'page' | 'all' | null
|
'page' | 'all' | null
|
||||||
>(null);
|
>(null);
|
||||||
const [codexQuota, setCodexQuota] = useState<Record<string, CodexQuotaState>>({});
|
|
||||||
const [codexLoading, setCodexLoading] = useState(false);
|
const [codexLoading, setCodexLoading] = useState(false);
|
||||||
const [codexLoadingScope, setCodexLoadingScope] = useState<'page' | 'all' | null>(null);
|
const [codexLoadingScope, setCodexLoadingScope] = useState<'page' | 'all' | null>(null);
|
||||||
const [geminiCliQuota, setGeminiCliQuota] = useState<Record<string, GeminiCliQuotaState>>({});
|
|
||||||
const [geminiCliLoading, setGeminiCliLoading] = useState(false);
|
const [geminiCliLoading, setGeminiCliLoading] = useState(false);
|
||||||
const [geminiCliLoadingScope, setGeminiCliLoadingScope] = useState<
|
const [geminiCliLoadingScope, setGeminiCliLoadingScope] = useState<
|
||||||
'page' | 'all' | null
|
'page' | 'all' | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
|
const antigravityQuota = useQuotaStore((state) => state.antigravityQuota);
|
||||||
|
const setAntigravityQuota = useQuotaStore((state) => state.setAntigravityQuota);
|
||||||
|
const codexQuota = useQuotaStore((state) => state.codexQuota);
|
||||||
|
const setCodexQuota = useQuotaStore((state) => state.setCodexQuota);
|
||||||
|
const geminiCliQuota = useQuotaStore((state) => state.geminiCliQuota);
|
||||||
|
const setGeminiCliQuota = useQuotaStore((state) => state.setGeminiCliQuota);
|
||||||
|
|
||||||
const antigravityLoadingRef = useRef(false);
|
const antigravityLoadingRef = useRef(false);
|
||||||
const antigravityRequestIdRef = useRef(0);
|
const antigravityRequestIdRef = useRef(0);
|
||||||
const codexLoadingRef = useRef(false);
|
const codexLoadingRef = useRef(false);
|
||||||
@@ -888,35 +869,56 @@ export function QuotaPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[fetchAntigravityQuota, t]
|
[fetchAntigravityQuota, setAntigravityQuota, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const buildCodexQuotaWindows = useCallback(
|
const buildCodexQuotaWindows = useCallback(
|
||||||
(payload: CodexUsagePayload): CodexQuotaWindow[] => {
|
(payload: CodexUsagePayload): CodexQuotaWindow[] => {
|
||||||
const rateLimit = payload.rate_limit ?? payload.rateLimit ?? undefined;
|
const rateLimit = payload.rate_limit ?? payload.rateLimit ?? undefined;
|
||||||
const codeReviewLimit = payload.code_review_rate_limit ?? payload.codeReviewRateLimit ?? undefined;
|
const codeReviewLimit =
|
||||||
|
payload.code_review_rate_limit ?? payload.codeReviewRateLimit ?? undefined;
|
||||||
const windows: CodexQuotaWindow[] = [];
|
const windows: CodexQuotaWindow[] = [];
|
||||||
const addWindow = (id: string, label: string, window?: CodexUsageWindow | null) => {
|
const addWindow = (
|
||||||
|
id: string,
|
||||||
|
label: string,
|
||||||
|
window?: CodexUsageWindow | null,
|
||||||
|
limitReached?: boolean,
|
||||||
|
allowed?: boolean
|
||||||
|
) => {
|
||||||
if (!window) return;
|
if (!window) return;
|
||||||
const usedPercent = normalizeNumberValue(window.used_percent ?? window.usedPercent);
|
const resetLabel = formatCodexResetLabel(window);
|
||||||
|
const usedPercentRaw = normalizeNumberValue(window.used_percent ?? window.usedPercent);
|
||||||
|
const isLimitReached = Boolean(limitReached) || allowed === false;
|
||||||
|
const usedPercent =
|
||||||
|
usedPercentRaw ?? (isLimitReached && resetLabel !== '-' ? 100 : null);
|
||||||
windows.push({
|
windows.push({
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
usedPercent,
|
usedPercent,
|
||||||
resetLabel: formatCodexResetLabel(window)
|
resetLabel
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
addWindow('primary', t('codex_quota.primary_window'), rateLimit?.primary_window ?? rateLimit?.primaryWindow);
|
addWindow(
|
||||||
|
'primary',
|
||||||
|
t('codex_quota.primary_window'),
|
||||||
|
rateLimit?.primary_window ?? rateLimit?.primaryWindow,
|
||||||
|
rateLimit?.limit_reached ?? rateLimit?.limitReached,
|
||||||
|
rateLimit?.allowed
|
||||||
|
);
|
||||||
addWindow(
|
addWindow(
|
||||||
'secondary',
|
'secondary',
|
||||||
t('codex_quota.secondary_window'),
|
t('codex_quota.secondary_window'),
|
||||||
rateLimit?.secondary_window ?? rateLimit?.secondaryWindow
|
rateLimit?.secondary_window ?? rateLimit?.secondaryWindow,
|
||||||
|
rateLimit?.limit_reached ?? rateLimit?.limitReached,
|
||||||
|
rateLimit?.allowed
|
||||||
);
|
);
|
||||||
addWindow(
|
addWindow(
|
||||||
'code-review',
|
'code-review',
|
||||||
t('codex_quota.code_review_window'),
|
t('codex_quota.code_review_window'),
|
||||||
codeReviewLimit?.primary_window ?? codeReviewLimit?.primaryWindow
|
codeReviewLimit?.primary_window ?? codeReviewLimit?.primaryWindow,
|
||||||
|
codeReviewLimit?.limit_reached ?? codeReviewLimit?.limitReached,
|
||||||
|
codeReviewLimit?.allowed
|
||||||
);
|
);
|
||||||
|
|
||||||
return windows;
|
return windows;
|
||||||
@@ -925,7 +927,9 @@ export function QuotaPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fetchCodexQuota = useCallback(
|
const fetchCodexQuota = useCallback(
|
||||||
async (file: AuthFileItem): Promise<{ planType: string | null; windows: CodexQuotaWindow[] }> => {
|
async (
|
||||||
|
file: AuthFileItem
|
||||||
|
): Promise<{ planType: string | null; windows: CodexQuotaWindow[] }> => {
|
||||||
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
|
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
|
||||||
const authIndex = normalizeAuthIndexValue(rawAuthIndex);
|
const authIndex = normalizeAuthIndexValue(rawAuthIndex);
|
||||||
if (!authIndex) {
|
if (!authIndex) {
|
||||||
@@ -1030,7 +1034,7 @@ export function QuotaPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[fetchCodexQuota, t]
|
[fetchCodexQuota, setCodexQuota, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchGeminiCliQuota = useCallback(
|
const fetchGeminiCliQuota = useCallback(
|
||||||
@@ -1067,13 +1071,20 @@ export function QuotaPage() {
|
|||||||
const modelId = normalizeStringValue(bucket.modelId ?? bucket.model_id);
|
const modelId = normalizeStringValue(bucket.modelId ?? bucket.model_id);
|
||||||
if (!modelId) return null;
|
if (!modelId) return null;
|
||||||
const tokenType = normalizeStringValue(bucket.tokenType ?? bucket.token_type);
|
const tokenType = normalizeStringValue(bucket.tokenType ?? bucket.token_type);
|
||||||
const remainingFraction = normalizeNumberValue(
|
const remainingFractionRaw = normalizeQuotaFraction(
|
||||||
bucket.remainingFraction ?? bucket.remaining_fraction
|
bucket.remainingFraction ?? bucket.remaining_fraction
|
||||||
);
|
);
|
||||||
const remainingAmount = normalizeNumberValue(
|
const remainingAmount = normalizeNumberValue(
|
||||||
bucket.remainingAmount ?? bucket.remaining_amount
|
bucket.remainingAmount ?? bucket.remaining_amount
|
||||||
);
|
);
|
||||||
const resetTime = normalizeStringValue(bucket.resetTime ?? bucket.reset_time) ?? undefined;
|
const resetTime = normalizeStringValue(bucket.resetTime ?? bucket.reset_time) ?? undefined;
|
||||||
|
let fallbackFraction: number | null = null;
|
||||||
|
if (remainingAmount !== null) {
|
||||||
|
fallbackFraction = remainingAmount <= 0 ? 0 : null;
|
||||||
|
} else if (resetTime) {
|
||||||
|
fallbackFraction = 0;
|
||||||
|
}
|
||||||
|
const remainingFraction = remainingFractionRaw ?? fallbackFraction;
|
||||||
return {
|
return {
|
||||||
id: `${modelId}-${tokenType ?? index}`,
|
id: `${modelId}-${tokenType ?? index}`,
|
||||||
label: modelId,
|
label: modelId,
|
||||||
@@ -1149,7 +1160,7 @@ export function QuotaPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[fetchGeminiCliQuota, t]
|
[fetchGeminiCliQuota, setGeminiCliQuota, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1157,6 +1168,7 @@ export function QuotaPage() {
|
|||||||
}, [loadFiles]);
|
}, [loadFiles]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (loading) return;
|
||||||
if (antigravityFiles.length === 0) {
|
if (antigravityFiles.length === 0) {
|
||||||
setAntigravityQuota({});
|
setAntigravityQuota({});
|
||||||
return;
|
return;
|
||||||
@@ -1171,9 +1183,10 @@ export function QuotaPage() {
|
|||||||
});
|
});
|
||||||
return nextState;
|
return nextState;
|
||||||
});
|
});
|
||||||
}, [antigravityFiles]);
|
}, [antigravityFiles, loading, setAntigravityQuota]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (loading) return;
|
||||||
if (codexFiles.length === 0) {
|
if (codexFiles.length === 0) {
|
||||||
setCodexQuota({});
|
setCodexQuota({});
|
||||||
return;
|
return;
|
||||||
@@ -1188,9 +1201,10 @@ export function QuotaPage() {
|
|||||||
});
|
});
|
||||||
return nextState;
|
return nextState;
|
||||||
});
|
});
|
||||||
}, [codexFiles]);
|
}, [codexFiles, loading, setCodexQuota]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (loading) return;
|
||||||
if (geminiCliFiles.length === 0) {
|
if (geminiCliFiles.length === 0) {
|
||||||
setGeminiCliQuota({});
|
setGeminiCliQuota({});
|
||||||
return;
|
return;
|
||||||
@@ -1205,7 +1219,7 @@ export function QuotaPage() {
|
|||||||
});
|
});
|
||||||
return nextState;
|
return nextState;
|
||||||
});
|
});
|
||||||
}, [geminiCliFiles]);
|
}, [geminiCliFiles, loading, setGeminiCliQuota]);
|
||||||
|
|
||||||
// Resolve type label text for badges.
|
// Resolve type label text for badges.
|
||||||
const getTypeLabel = (type: string): string => {
|
const getTypeLabel = (type: string): string => {
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ export { useLanguageStore } from './useLanguageStore';
|
|||||||
export { useAuthStore } from './useAuthStore';
|
export { useAuthStore } from './useAuthStore';
|
||||||
export { useConfigStore } from './useConfigStore';
|
export { useConfigStore } from './useConfigStore';
|
||||||
export { useModelsStore } from './useModelsStore';
|
export { useModelsStore } from './useModelsStore';
|
||||||
|
export { useQuotaStore } from './useQuotaStore';
|
||||||
|
|||||||
49
src/stores/useQuotaStore.ts
Normal file
49
src/stores/useQuotaStore.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Quota cache that survives route switches.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import type { AntigravityQuotaState, CodexQuotaState, GeminiCliQuotaState } from '@/types';
|
||||||
|
|
||||||
|
type QuotaUpdater<T> = T | ((prev: T) => T);
|
||||||
|
|
||||||
|
interface QuotaStoreState {
|
||||||
|
antigravityQuota: Record<string, AntigravityQuotaState>;
|
||||||
|
codexQuota: Record<string, CodexQuotaState>;
|
||||||
|
geminiCliQuota: Record<string, GeminiCliQuotaState>;
|
||||||
|
setAntigravityQuota: (updater: QuotaUpdater<Record<string, AntigravityQuotaState>>) => void;
|
||||||
|
setCodexQuota: (updater: QuotaUpdater<Record<string, CodexQuotaState>>) => void;
|
||||||
|
setGeminiCliQuota: (updater: QuotaUpdater<Record<string, GeminiCliQuotaState>>) => void;
|
||||||
|
clearQuotaCache: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveUpdater = <T,>(updater: QuotaUpdater<T>, prev: T): T => {
|
||||||
|
if (typeof updater === 'function') {
|
||||||
|
return (updater as (value: T) => T)(prev);
|
||||||
|
}
|
||||||
|
return updater;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useQuotaStore = create<QuotaStoreState>((set) => ({
|
||||||
|
antigravityQuota: {},
|
||||||
|
codexQuota: {},
|
||||||
|
geminiCliQuota: {},
|
||||||
|
setAntigravityQuota: (updater) =>
|
||||||
|
set((state) => ({
|
||||||
|
antigravityQuota: resolveUpdater(updater, state.antigravityQuota)
|
||||||
|
})),
|
||||||
|
setCodexQuota: (updater) =>
|
||||||
|
set((state) => ({
|
||||||
|
codexQuota: resolveUpdater(updater, state.codexQuota)
|
||||||
|
})),
|
||||||
|
setGeminiCliQuota: (updater) =>
|
||||||
|
set((state) => ({
|
||||||
|
geminiCliQuota: resolveUpdater(updater, state.geminiCliQuota)
|
||||||
|
})),
|
||||||
|
clearQuotaCache: () =>
|
||||||
|
set({
|
||||||
|
antigravityQuota: {},
|
||||||
|
codexQuota: {},
|
||||||
|
geminiCliQuota: {}
|
||||||
|
})
|
||||||
|
}));
|
||||||
@@ -12,3 +12,4 @@ export * from './authFile';
|
|||||||
export * from './oauth';
|
export * from './oauth';
|
||||||
export * from './usage';
|
export * from './usage';
|
||||||
export * from './log';
|
export * from './log';
|
||||||
|
export * from './quota';
|
||||||
|
|||||||
49
src/types/quota.ts
Normal file
49
src/types/quota.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Quota management types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AntigravityQuotaGroup {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
models: string[];
|
||||||
|
remainingFraction: number;
|
||||||
|
resetTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AntigravityQuotaState {
|
||||||
|
status: 'idle' | 'loading' | 'success' | 'error';
|
||||||
|
groups: AntigravityQuotaGroup[];
|
||||||
|
error?: string;
|
||||||
|
errorStatus?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeminiCliQuotaBucketState {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
remainingFraction: number | null;
|
||||||
|
remainingAmount: number | null;
|
||||||
|
resetTime: string | undefined;
|
||||||
|
tokenType: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeminiCliQuotaState {
|
||||||
|
status: 'idle' | 'loading' | 'success' | 'error';
|
||||||
|
buckets: GeminiCliQuotaBucketState[];
|
||||||
|
error?: string;
|
||||||
|
errorStatus?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexQuotaWindow {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
usedPercent: number | null;
|
||||||
|
resetLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexQuotaState {
|
||||||
|
status: 'idle' | 'loading' | 'success' | 'error';
|
||||||
|
windows: CodexQuotaWindow[];
|
||||||
|
planType?: string | null;
|
||||||
|
error?: string;
|
||||||
|
errorStatus?: number;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user