mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 18:50:49 +08:00
fix(quota): classify codex windows by 5-hour and weekly limits
This commit is contained in:
@@ -10,13 +10,14 @@ import type {
|
|||||||
AntigravityModelsPayload,
|
AntigravityModelsPayload,
|
||||||
AntigravityQuotaState,
|
AntigravityQuotaState,
|
||||||
AuthFileItem,
|
AuthFileItem,
|
||||||
|
CodexRateLimitInfo,
|
||||||
CodexQuotaState,
|
CodexQuotaState,
|
||||||
CodexUsageWindow,
|
CodexUsageWindow,
|
||||||
CodexQuotaWindow,
|
CodexQuotaWindow,
|
||||||
CodexUsagePayload,
|
CodexUsagePayload,
|
||||||
GeminiCliParsedBucket,
|
GeminiCliParsedBucket,
|
||||||
GeminiCliQuotaBucketState,
|
GeminiCliQuotaBucketState,
|
||||||
GeminiCliQuotaState
|
GeminiCliQuotaState,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
import { apiCallApi, authFilesApi, getApiCallErrorMessage } from '@/services/api';
|
import { apiCallApi, authFilesApi, getApiCallErrorMessage } from '@/services/api';
|
||||||
import {
|
import {
|
||||||
@@ -47,7 +48,7 @@ import {
|
|||||||
isCodexFile,
|
isCodexFile,
|
||||||
isDisabledAuthFile,
|
isDisabledAuthFile,
|
||||||
isGeminiCliFile,
|
isGeminiCliFile,
|
||||||
isRuntimeOnlyAuthFile
|
isRuntimeOnlyAuthFile,
|
||||||
} from '@/utils/quota';
|
} from '@/utils/quota';
|
||||||
import type { QuotaRenderHelpers } from './QuotaCard';
|
import type { QuotaRenderHelpers } from './QuotaCard';
|
||||||
import styles from '@/pages/QuotaPage.module.scss';
|
import styles from '@/pages/QuotaPage.module.scss';
|
||||||
@@ -142,7 +143,7 @@ const fetchAntigravityQuota = async (
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
url,
|
url,
|
||||||
header: { ...ANTIGRAVITY_REQUEST_HEADERS },
|
header: { ...ANTIGRAVITY_REQUEST_HEADERS },
|
||||||
data: requestBody
|
data: requestBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.statusCode < 200 || result.statusCode >= 300) {
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
||||||
@@ -189,6 +190,15 @@ const fetchAntigravityQuota = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): CodexQuotaWindow[] => {
|
const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): CodexQuotaWindow[] => {
|
||||||
|
const FIVE_HOUR_SECONDS = 18000;
|
||||||
|
const WEEK_SECONDS = 604800;
|
||||||
|
const WINDOW_META = {
|
||||||
|
codeFiveHour: { id: 'five-hour', labelKey: 'codex_quota.primary_window' },
|
||||||
|
codeWeekly: { id: 'weekly', labelKey: 'codex_quota.secondary_window' },
|
||||||
|
codeReviewFiveHour: { id: 'code-review-five-hour', labelKey: 'codex_quota.code_review_primary_window' },
|
||||||
|
codeReviewWeekly: { id: 'code-review-weekly', labelKey: 'codex_quota.code_review_secondary_window' },
|
||||||
|
} as const;
|
||||||
|
|
||||||
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[] = [];
|
||||||
@@ -210,30 +220,74 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
|||||||
label: t(labelKey),
|
label: t(labelKey),
|
||||||
labelKey,
|
labelKey,
|
||||||
usedPercent,
|
usedPercent,
|
||||||
resetLabel
|
resetLabel,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getWindowSeconds = (window?: CodexUsageWindow | null): number | null => {
|
||||||
|
if (!window) return null;
|
||||||
|
return normalizeNumberValue(window.limit_window_seconds ?? window.limitWindowSeconds);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rawLimitReached = rateLimit?.limit_reached ?? rateLimit?.limitReached;
|
||||||
|
const rawAllowed = rateLimit?.allowed;
|
||||||
|
|
||||||
|
const pickClassifiedWindows = (
|
||||||
|
limitInfo?: CodexRateLimitInfo | null
|
||||||
|
): { fiveHourWindow: CodexUsageWindow | null; weeklyWindow: CodexUsageWindow | null } => {
|
||||||
|
const rawWindows = [
|
||||||
|
limitInfo?.primary_window ?? limitInfo?.primaryWindow ?? null,
|
||||||
|
limitInfo?.secondary_window ?? limitInfo?.secondaryWindow ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
|
let fiveHourWindow: CodexUsageWindow | null = null;
|
||||||
|
let weeklyWindow: CodexUsageWindow | null = null;
|
||||||
|
|
||||||
|
for (const window of rawWindows) {
|
||||||
|
if (!window) continue;
|
||||||
|
const seconds = getWindowSeconds(window);
|
||||||
|
if (seconds === FIVE_HOUR_SECONDS && !fiveHourWindow) {
|
||||||
|
fiveHourWindow = window;
|
||||||
|
} else if (seconds === WEEK_SECONDS && !weeklyWindow) {
|
||||||
|
weeklyWindow = window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fiveHourWindow, weeklyWindow };
|
||||||
|
};
|
||||||
|
|
||||||
|
const rateWindows = pickClassifiedWindows(rateLimit);
|
||||||
addWindow(
|
addWindow(
|
||||||
'primary',
|
WINDOW_META.codeFiveHour.id,
|
||||||
'codex_quota.primary_window',
|
WINDOW_META.codeFiveHour.labelKey,
|
||||||
rateLimit?.primary_window ?? rateLimit?.primaryWindow,
|
rateWindows.fiveHourWindow,
|
||||||
rateLimit?.limit_reached ?? rateLimit?.limitReached,
|
rawLimitReached,
|
||||||
rateLimit?.allowed
|
rawAllowed
|
||||||
);
|
);
|
||||||
addWindow(
|
addWindow(
|
||||||
'secondary',
|
WINDOW_META.codeWeekly.id,
|
||||||
'codex_quota.secondary_window',
|
WINDOW_META.codeWeekly.labelKey,
|
||||||
rateLimit?.secondary_window ?? rateLimit?.secondaryWindow,
|
rateWindows.weeklyWindow,
|
||||||
rateLimit?.limit_reached ?? rateLimit?.limitReached,
|
rawLimitReached,
|
||||||
rateLimit?.allowed
|
rawAllowed
|
||||||
|
);
|
||||||
|
|
||||||
|
const codeReviewWindows = pickClassifiedWindows(codeReviewLimit);
|
||||||
|
const codeReviewLimitReached = codeReviewLimit?.limit_reached ?? codeReviewLimit?.limitReached;
|
||||||
|
const codeReviewAllowed = codeReviewLimit?.allowed;
|
||||||
|
addWindow(
|
||||||
|
WINDOW_META.codeReviewFiveHour.id,
|
||||||
|
WINDOW_META.codeReviewFiveHour.labelKey,
|
||||||
|
codeReviewWindows.fiveHourWindow,
|
||||||
|
codeReviewLimitReached,
|
||||||
|
codeReviewAllowed
|
||||||
);
|
);
|
||||||
addWindow(
|
addWindow(
|
||||||
'code-review',
|
WINDOW_META.codeReviewWeekly.id,
|
||||||
'codex_quota.code_review_window',
|
WINDOW_META.codeReviewWeekly.labelKey,
|
||||||
codeReviewLimit?.primary_window ?? codeReviewLimit?.primaryWindow,
|
codeReviewWindows.weeklyWindow,
|
||||||
codeReviewLimit?.limit_reached ?? codeReviewLimit?.limitReached,
|
codeReviewLimitReached,
|
||||||
codeReviewLimit?.allowed
|
codeReviewAllowed
|
||||||
);
|
);
|
||||||
|
|
||||||
return windows;
|
return windows;
|
||||||
@@ -257,14 +311,14 @@ const fetchCodexQuota = async (
|
|||||||
|
|
||||||
const requestHeader: Record<string, string> = {
|
const requestHeader: Record<string, string> = {
|
||||||
...CODEX_REQUEST_HEADERS,
|
...CODEX_REQUEST_HEADERS,
|
||||||
'Chatgpt-Account-Id': accountId
|
'Chatgpt-Account-Id': accountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await apiCallApi.request({
|
const result = await apiCallApi.request({
|
||||||
authIndex,
|
authIndex,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: CODEX_USAGE_URL,
|
url: CODEX_USAGE_URL,
|
||||||
header: requestHeader
|
header: requestHeader,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.statusCode < 200 || result.statusCode >= 300) {
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
||||||
@@ -301,7 +355,7 @@ const fetchGeminiCliQuota = async (
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: GEMINI_CLI_QUOTA_URL,
|
url: GEMINI_CLI_QUOTA_URL,
|
||||||
header: { ...GEMINI_CLI_REQUEST_HEADERS },
|
header: { ...GEMINI_CLI_REQUEST_HEADERS },
|
||||||
data: JSON.stringify({ project: projectId })
|
data: JSON.stringify({ project: projectId }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.statusCode < 200 || result.statusCode >= 300) {
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
||||||
@@ -320,7 +374,9 @@ const fetchGeminiCliQuota = async (
|
|||||||
const remainingFractionRaw = normalizeQuotaFraction(
|
const remainingFractionRaw = normalizeQuotaFraction(
|
||||||
bucket.remainingFraction ?? bucket.remaining_fraction
|
bucket.remainingFraction ?? bucket.remaining_fraction
|
||||||
);
|
);
|
||||||
const remainingAmount = normalizeNumberValue(bucket.remainingAmount ?? bucket.remaining_amount);
|
const remainingAmount = normalizeNumberValue(
|
||||||
|
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;
|
let fallbackFraction: number | null = null;
|
||||||
if (remainingAmount !== null) {
|
if (remainingAmount !== null) {
|
||||||
@@ -334,7 +390,7 @@ const fetchGeminiCliQuota = async (
|
|||||||
tokenType,
|
tokenType,
|
||||||
remainingFraction,
|
remainingFraction,
|
||||||
remainingAmount,
|
remainingAmount,
|
||||||
resetTime
|
resetTime,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((bucket): bucket is GeminiCliParsedBucket => bucket !== null);
|
.filter((bucket): bucket is GeminiCliParsedBucket => bucket !== null);
|
||||||
@@ -366,11 +422,7 @@ const renderAntigravityItems = (
|
|||||||
h(
|
h(
|
||||||
'div',
|
'div',
|
||||||
{ className: styleMap.quotaRowHeader },
|
{ className: styleMap.quotaRowHeader },
|
||||||
h(
|
h('span', { className: styleMap.quotaModel, title: group.models.join(', ') }, group.label),
|
||||||
'span',
|
|
||||||
{ className: styleMap.quotaModel, title: group.models.join(', ') },
|
|
||||||
group.label
|
|
||||||
),
|
|
||||||
h(
|
h(
|
||||||
'div',
|
'div',
|
||||||
{ className: styleMap.quotaMeta },
|
{ className: styleMap.quotaMeta },
|
||||||
@@ -403,7 +455,6 @@ const renderCodexItems = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const planLabel = getPlanLabel(planType);
|
const planLabel = getPlanLabel(planType);
|
||||||
const isFreePlan = normalizePlanType(planType) === 'free';
|
|
||||||
const nodes: ReactNode[] = [];
|
const nodes: ReactNode[] = [];
|
||||||
|
|
||||||
if (planLabel) {
|
if (planLabel) {
|
||||||
@@ -417,17 +468,6 @@ const renderCodexItems = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFreePlan) {
|
|
||||||
nodes.push(
|
|
||||||
h(
|
|
||||||
'div',
|
|
||||||
{ key: 'warning', className: styleMap.quotaWarning },
|
|
||||||
t('codex_quota.no_access')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return h(Fragment, null, ...nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windows.length === 0) {
|
if (windows.length === 0) {
|
||||||
nodes.push(
|
nodes.push(
|
||||||
h('div', { key: 'empty', className: styleMap.quotaMessage }, t('codex_quota.empty_windows'))
|
h('div', { key: 'empty', className: styleMap.quotaMessage }, t('codex_quota.empty_windows'))
|
||||||
@@ -487,7 +527,7 @@ const renderGeminiCliItems = (
|
|||||||
bucket.remainingAmount === null || bucket.remainingAmount === undefined
|
bucket.remainingAmount === null || bucket.remainingAmount === undefined
|
||||||
? null
|
? null
|
||||||
: t('gemini_cli_quota.remaining_amount', {
|
: t('gemini_cli_quota.remaining_amount', {
|
||||||
count: bucket.remainingAmount
|
count: bucket.remainingAmount,
|
||||||
});
|
});
|
||||||
const titleBase =
|
const titleBase =
|
||||||
bucket.modelIds && bucket.modelIds.length > 0 ? bucket.modelIds.join(', ') : bucket.label;
|
bucket.modelIds && bucket.modelIds.length > 0 ? bucket.modelIds.join(', ') : bucket.label;
|
||||||
@@ -530,13 +570,13 @@ export const ANTIGRAVITY_CONFIG: QuotaConfig<AntigravityQuotaState, AntigravityQ
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
groups: [],
|
groups: [],
|
||||||
error: message,
|
error: message,
|
||||||
errorStatus: status
|
errorStatus: status,
|
||||||
}),
|
}),
|
||||||
cardClassName: styles.antigravityCard,
|
cardClassName: styles.antigravityCard,
|
||||||
controlsClassName: styles.antigravityControls,
|
controlsClassName: styles.antigravityControls,
|
||||||
controlClassName: styles.antigravityControl,
|
controlClassName: styles.antigravityControl,
|
||||||
gridClassName: styles.antigravityGrid,
|
gridClassName: styles.antigravityGrid,
|
||||||
renderQuotaItems: renderAntigravityItems
|
renderQuotaItems: renderAntigravityItems,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CODEX_CONFIG: QuotaConfig<
|
export const CODEX_CONFIG: QuotaConfig<
|
||||||
@@ -553,19 +593,19 @@ export const CODEX_CONFIG: QuotaConfig<
|
|||||||
buildSuccessState: (data) => ({
|
buildSuccessState: (data) => ({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
windows: data.windows,
|
windows: data.windows,
|
||||||
planType: data.planType
|
planType: data.planType,
|
||||||
}),
|
}),
|
||||||
buildErrorState: (message, status) => ({
|
buildErrorState: (message, status) => ({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
windows: [],
|
windows: [],
|
||||||
error: message,
|
error: message,
|
||||||
errorStatus: status
|
errorStatus: status,
|
||||||
}),
|
}),
|
||||||
cardClassName: styles.codexCard,
|
cardClassName: styles.codexCard,
|
||||||
controlsClassName: styles.codexControls,
|
controlsClassName: styles.codexControls,
|
||||||
controlClassName: styles.codexControl,
|
controlClassName: styles.codexControl,
|
||||||
gridClassName: styles.codexGrid,
|
gridClassName: styles.codexGrid,
|
||||||
renderQuotaItems: renderCodexItems
|
renderQuotaItems: renderCodexItems,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GEMINI_CLI_CONFIG: QuotaConfig<GeminiCliQuotaState, GeminiCliQuotaBucketState[]> = {
|
export const GEMINI_CLI_CONFIG: QuotaConfig<GeminiCliQuotaState, GeminiCliQuotaBucketState[]> = {
|
||||||
@@ -582,11 +622,11 @@ export const GEMINI_CLI_CONFIG: QuotaConfig<GeminiCliQuotaState, GeminiCliQuotaB
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
buckets: [],
|
buckets: [],
|
||||||
error: message,
|
error: message,
|
||||||
errorStatus: status
|
errorStatus: status,
|
||||||
}),
|
}),
|
||||||
cardClassName: styles.geminiCliCard,
|
cardClassName: styles.geminiCliCard,
|
||||||
controlsClassName: styles.geminiCliControls,
|
controlsClassName: styles.geminiCliControls,
|
||||||
controlClassName: styles.geminiCliControl,
|
controlClassName: styles.geminiCliControl,
|
||||||
gridClassName: styles.geminiCliGrid,
|
gridClassName: styles.geminiCliGrid,
|
||||||
renderQuotaItems: renderGeminiCliItems
|
renderQuotaItems: renderGeminiCliItems,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -445,7 +445,8 @@
|
|||||||
"fetch_all": "Fetch All",
|
"fetch_all": "Fetch All",
|
||||||
"primary_window": "5-hour limit",
|
"primary_window": "5-hour limit",
|
||||||
"secondary_window": "Weekly limit",
|
"secondary_window": "Weekly limit",
|
||||||
"code_review_window": "Code review limit",
|
"code_review_primary_window": "Code review 5-hour limit",
|
||||||
|
"code_review_secondary_window": "Code review weekly limit",
|
||||||
"plan_label": "Plan",
|
"plan_label": "Plan",
|
||||||
"plan_plus": "Plus",
|
"plan_plus": "Plus",
|
||||||
"plan_team": "Team",
|
"plan_team": "Team",
|
||||||
|
|||||||
@@ -445,7 +445,8 @@
|
|||||||
"fetch_all": "获取全部",
|
"fetch_all": "获取全部",
|
||||||
"primary_window": "5 小时限额",
|
"primary_window": "5 小时限额",
|
||||||
"secondary_window": "周限额",
|
"secondary_window": "周限额",
|
||||||
"code_review_window": "代码审查限额",
|
"code_review_primary_window": "代码审查 5 小时限额",
|
||||||
|
"code_review_secondary_window": "代码审查周限额",
|
||||||
"plan_label": "套餐",
|
"plan_label": "套餐",
|
||||||
"plan_plus": "Plus",
|
"plan_plus": "Plus",
|
||||||
"plan_team": "Team",
|
"plan_team": "Team",
|
||||||
|
|||||||
Reference in New Issue
Block a user