diff --git a/web/src/components/table/channels/modals/CodexUsageModal.jsx b/web/src/components/table/channels/modals/CodexUsageModal.jsx index 5e1317ac..76c84b90 100644 --- a/web/src/components/table/channels/modals/CodexUsageModal.jsx +++ b/web/src/components/table/channels/modals/CodexUsageModal.jsx @@ -43,6 +43,68 @@ const pickStrokeColor = (percent) => { return '#3b82f6'; }; +const normalizePlanType = (value) => { + if (value == null) return ''; + return String(value).trim().toLowerCase(); +}; + +const getWindowDurationSeconds = (windowData) => { + const value = Number(windowData?.limit_window_seconds); + if (!Number.isFinite(value) || value <= 0) return null; + return value; +}; + +const classifyWindowByDuration = (windowData) => { + const seconds = getWindowDurationSeconds(windowData); + if (seconds == null) return null; + return seconds >= 24 * 60 * 60 ? 'weekly' : 'fiveHour'; +}; + +const resolveRateLimitWindows = (data) => { + const rateLimit = data?.rate_limit ?? {}; + const primary = rateLimit?.primary_window ?? null; + const secondary = rateLimit?.secondary_window ?? null; + const windows = [primary, secondary].filter(Boolean); + const planType = normalizePlanType(data?.plan_type ?? rateLimit?.plan_type); + + let fiveHourWindow = null; + let weeklyWindow = null; + + for (const windowData of windows) { + const bucket = classifyWindowByDuration(windowData); + if (bucket === 'fiveHour' && !fiveHourWindow) { + fiveHourWindow = windowData; + continue; + } + if (bucket === 'weekly' && !weeklyWindow) { + weeklyWindow = windowData; + } + } + + if (planType === 'free') { + if (!weeklyWindow) { + weeklyWindow = primary ?? secondary ?? null; + } + return { fiveHourWindow: null, weeklyWindow }; + } + + if (!fiveHourWindow && !weeklyWindow) { + return { + fiveHourWindow: primary ?? null, + weeklyWindow: secondary ?? null, + }; + } + + if (!fiveHourWindow) { + fiveHourWindow = windows.find((windowData) => windowData !== weeklyWindow) ?? null; + } + if (!weeklyWindow) { + weeklyWindow = windows.find((windowData) => windowData !== fiveHourWindow) ?? null; + } + + return { fiveHourWindow, weeklyWindow }; +}; + const formatDurationSeconds = (seconds, t) => { const tt = typeof t === 'function' ? t : (v) => v; const s = Number(seconds); @@ -68,6 +130,10 @@ const formatUnixSeconds = (unixSeconds) => { const RateLimitWindowCard = ({ t, title, windowData }) => { const tt = typeof t === 'function' ? t : (v) => v; + const hasWindowData = + !!windowData && + typeof windowData === 'object' && + Object.keys(windowData).length > 0; const percent = clampPercent(windowData?.used_percent ?? 0); const resetAt = windowData?.reset_at; const resetAfterSeconds = windowData?.reset_after_seconds; @@ -83,26 +149,30 @@ const RateLimitWindowCard = ({ t, title, windowData }) => { -
- -
+ {hasWindowData ? ( +
+ +
+ ) : ( +
-
+ )}
{tt('已使用:')} - {percent}% + {hasWindowData ? `${percent}%` : '-'}
{tt('距离重置:')} - {formatDurationSeconds(resetAfterSeconds, tt)} + {hasWindowData ? formatDurationSeconds(resetAfterSeconds, tt) : '-'}
{tt('窗口:')} - {formatDurationSeconds(limitWindowSeconds, tt)} + {hasWindowData ? formatDurationSeconds(limitWindowSeconds, tt) : '-'}
@@ -113,9 +183,7 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => { const tt = typeof t === 'function' ? t : (v) => v; const data = payload?.data ?? null; const rateLimit = data?.rate_limit ?? {}; - - const primary = rateLimit?.primary_window ?? null; - const secondary = rateLimit?.secondary_window ?? null; + const { fiveHourWindow, weeklyWindow } = resolveRateLimitWindows(data); const allowed = !!rateLimit?.allowed; const limitReached = !!rateLimit?.limit_reached; @@ -163,12 +231,12 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {