From 215ce61b48c386b16769f020947f9ff579ccb0e3 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Tue, 30 Dec 2025 00:17:51 +0800 Subject: [PATCH] fix: error display --- src/i18n/locales/en.json | 2 + src/i18n/locales/zh-CN.json | 2 + src/pages/AuthFilesPage.tsx | 74 +++++++++++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index b741e1a..e647242 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -34,6 +34,8 @@ "alias": "Alias", "failure": "Failure", "unknown_error": "Unknown error", + "quota_update_required": "Please update the CPA version or check for updates", + "quota_check_credential": "Please check the credential status", "copy": "Copy", "custom_headers_label": "Custom Headers", "custom_headers_hint": "Optional HTTP headers to send with the request. Leave blank to remove.", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index aedfa07..38f8d57 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -34,6 +34,8 @@ "alias": "别名", "failure": "失败", "unknown_error": "未知错误", + "quota_update_required": "请更新 CPA 版本或检查更新", + "quota_check_credential": "请检查凭证状态", "copy": "复制", "custom_headers_label": "自定义请求头", "custom_headers_hint": "可选,设置需要附带到请求中的 HTTP 头,名称和值均不能为空。", diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index 1b78cf6..d36b58c 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -96,6 +96,7 @@ interface AntigravityQuotaState { status: 'idle' | 'loading' | 'success' | 'error'; groups: AntigravityQuotaGroup[]; error?: string; + errorStatus?: number; } interface AntigravityQuotaInfo { @@ -213,6 +214,7 @@ interface CodexQuotaState { windows: CodexQuotaWindow[]; planType?: string | null; error?: string; + errorStatus?: number; } const CODEX_USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage'; @@ -223,6 +225,28 @@ const CODEX_REQUEST_HEADERS = { 'User-Agent': 'codex_cli_rs/0.76.0 (Debian 13.0.0; x86_64) WindowsTerminal' }; +const createStatusError = (message: string, status?: number) => { + const error = new Error(message) as Error & { status?: number }; + if (status !== undefined) { + error.status = status; + } + return error; +}; + +const getStatusFromError = (err: unknown): number | undefined => { + if (typeof err === 'object' && err !== null && 'status' in err) { + const rawStatus = (err as { status?: unknown }).status; + if (typeof rawStatus === 'number' && Number.isFinite(rawStatus)) { + return rawStatus; + } + const asNumber = Number(rawStatus); + if (Number.isFinite(asNumber) && asNumber > 0) { + return asNumber; + } + } + return undefined; +}; + // 标准化 auth_index 值(与 usage.ts 中的 normalizeAuthIndex 保持一致) @@ -780,6 +804,8 @@ export function AuthFilesPage() { const fetchAntigravityQuota = useCallback( async (authIndex: string): Promise => { let lastError = ''; + let lastStatus: number | undefined; + let priorityStatus: number | undefined; let hadSuccess = false; for (const url of ANTIGRAVITY_QUOTA_URLS) { @@ -794,6 +820,10 @@ export function AuthFilesPage() { if (result.statusCode < 200 || result.statusCode >= 300) { lastError = getApiCallErrorMessage(result); + lastStatus = result.statusCode; + if (result.statusCode === 403 || result.statusCode === 404) { + priorityStatus ??= result.statusCode; + } continue; } @@ -814,6 +844,13 @@ export function AuthFilesPage() { return groups; } catch (err: unknown) { lastError = err instanceof Error ? err.message : t('common.unknown_error'); + const status = getStatusFromError(err); + if (status) { + lastStatus = status; + if (status === 403 || status === 404) { + priorityStatus ??= status; + } + } } } @@ -821,7 +858,7 @@ export function AuthFilesPage() { return []; } - throw new Error(lastError || t('common.unknown_error')); + throw createStatusError(lastError || t('common.unknown_error'), priorityStatus ?? lastStatus); }, [t] ); @@ -862,7 +899,8 @@ export function AuthFilesPage() { return { name: file.name, status: 'success' as const, groups }; } catch (err: unknown) { const message = err instanceof Error ? err.message : t('common.unknown_error'); - return { name: file.name, status: 'error' as const, error: message }; + const errorStatus = getStatusFromError(err); + return { name: file.name, status: 'error' as const, error: message, errorStatus }; } }) ); @@ -881,7 +919,8 @@ export function AuthFilesPage() { nextState[result.name] = { status: 'error', groups: [], - error: result.error + error: result.error, + errorStatus: result.errorStatus }; } }); @@ -953,7 +992,7 @@ export function AuthFilesPage() { header: requestHeader }); if (result.statusCode < 200 || result.statusCode >= 300) { - throw new Error(getApiCallErrorMessage(result)); + throw createStatusError(getApiCallErrorMessage(result), result.statusCode); } const payload = parseCodexUsagePayload(result.body ?? result.bodyText); if (!payload) { @@ -1001,7 +1040,8 @@ export function AuthFilesPage() { return { name: file.name, status: 'success' as const, planType, windows }; } catch (err: unknown) { const message = err instanceof Error ? err.message : t('common.unknown_error'); - return { name: file.name, status: 'error' as const, error: message }; + const errorStatus = getStatusFromError(err); + return { name: file.name, status: 'error' as const, error: message, errorStatus }; } }) ); @@ -1021,7 +1061,8 @@ export function AuthFilesPage() { nextState[result.name] = { status: 'error', windows: [], - error: result.error + error: result.error, + errorStatus: result.errorStatus }; } }); @@ -1383,6 +1424,15 @@ export function AuthFilesPage() { if (normalized === 'free') return t('codex_quota.plan_free'); return planType || normalized; }; + + const getQuotaErrorMessage = useCallback( + (status: number | undefined, fallback: string) => { + if (status === 404) return t('common.quota_update_required'); + if (status === 403) return t('common.quota_check_credential'); + return fallback; + }, + [t] + ); // OAuth 排除相关方法 const openExcludedModal = (provider?: string) => { @@ -1627,6 +1677,10 @@ export function AuthFilesPage() { const quotaState = antigravityQuota[item.name]; const quotaStatus = quotaState?.status ?? 'idle'; const quotaGroups = quotaState?.groups ?? []; + const quotaErrorMessage = getQuotaErrorMessage( + quotaState?.errorStatus, + quotaState?.error || t('common.unknown_error') + ); return (
@@ -1652,7 +1706,7 @@ export function AuthFilesPage() { ) : quotaStatus === 'error' ? (
{t('antigravity_quota.load_failed', { - message: quotaState?.error || t('common.unknown_error') + message: quotaErrorMessage })}
) : quotaGroups.length === 0 ? ( @@ -1703,6 +1757,10 @@ export function AuthFilesPage() { const planType = quotaState?.planType ?? null; const planLabel = getCodexPlanLabel(planType); const isFreePlan = normalizePlanType(planType) === 'free'; + const quotaErrorMessage = getQuotaErrorMessage( + quotaState?.errorStatus, + quotaState?.error || t('common.unknown_error') + ); return (
@@ -1728,7 +1786,7 @@ export function AuthFilesPage() { ) : quotaStatus === 'error' ? (
{t('codex_quota.load_failed', { - message: quotaState?.error || t('common.unknown_error') + message: quotaErrorMessage })}
) : (