|
@@ -43,6 +43,68 @@ const pickStrokeColor = (percent) => {
|
|
|
return '#3b82f6';
|
|
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 formatDurationSeconds = (seconds, t) => {
|
|
|
const tt = typeof t === 'function' ? t : (v) => v;
|
|
const tt = typeof t === 'function' ? t : (v) => v;
|
|
|
const s = Number(seconds);
|
|
const s = Number(seconds);
|
|
@@ -68,6 +130,10 @@ const formatUnixSeconds = (unixSeconds) => {
|
|
|
|
|
|
|
|
const RateLimitWindowCard = ({ t, title, windowData }) => {
|
|
const RateLimitWindowCard = ({ t, title, windowData }) => {
|
|
|
const tt = typeof t === 'function' ? t : (v) => v;
|
|
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 percent = clampPercent(windowData?.used_percent ?? 0);
|
|
|
const resetAt = windowData?.reset_at;
|
|
const resetAt = windowData?.reset_at;
|
|
|
const resetAfterSeconds = windowData?.reset_after_seconds;
|
|
const resetAfterSeconds = windowData?.reset_after_seconds;
|
|
@@ -83,26 +149,30 @@ const RateLimitWindowCard = ({ t, title, windowData }) => {
|
|
|
</Text>
|
|
</Text>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div className='mt-2'>
|
|
|
|
|
- <Progress
|
|
|
|
|
- percent={percent}
|
|
|
|
|
- stroke={pickStrokeColor(percent)}
|
|
|
|
|
- showInfo={true}
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {hasWindowData ? (
|
|
|
|
|
+ <div className='mt-2'>
|
|
|
|
|
+ <Progress
|
|
|
|
|
+ percent={percent}
|
|
|
|
|
+ stroke={pickStrokeColor(percent)}
|
|
|
|
|
+ showInfo={true}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <div className='mt-3 text-sm text-semi-color-text-2'>-</div>
|
|
|
|
|
+ )}
|
|
|
|
|
|
|
|
<div className='mt-1 flex flex-wrap items-center gap-2 text-xs text-semi-color-text-2'>
|
|
<div className='mt-1 flex flex-wrap items-center gap-2 text-xs text-semi-color-text-2'>
|
|
|
<div>
|
|
<div>
|
|
|
{tt('已使用:')}
|
|
{tt('已使用:')}
|
|
|
- {percent}%
|
|
|
|
|
|
|
+ {hasWindowData ? `${percent}%` : '-'}
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
{tt('距离重置:')}
|
|
{tt('距离重置:')}
|
|
|
- {formatDurationSeconds(resetAfterSeconds, tt)}
|
|
|
|
|
|
|
+ {hasWindowData ? formatDurationSeconds(resetAfterSeconds, tt) : '-'}
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
{tt('窗口:')}
|
|
{tt('窗口:')}
|
|
|
- {formatDurationSeconds(limitWindowSeconds, tt)}
|
|
|
|
|
|
|
+ {hasWindowData ? formatDurationSeconds(limitWindowSeconds, tt) : '-'}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -113,9 +183,7 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
|
|
|
const tt = typeof t === 'function' ? t : (v) => v;
|
|
const tt = typeof t === 'function' ? t : (v) => v;
|
|
|
const data = payload?.data ?? null;
|
|
const data = payload?.data ?? null;
|
|
|
const rateLimit = data?.rate_limit ?? {};
|
|
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 allowed = !!rateLimit?.allowed;
|
|
|
const limitReached = !!rateLimit?.limit_reached;
|
|
const limitReached = !!rateLimit?.limit_reached;
|
|
@@ -163,12 +231,12 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
|
|
|
<RateLimitWindowCard
|
|
<RateLimitWindowCard
|
|
|
t={tt}
|
|
t={tt}
|
|
|
title={tt('5小时窗口')}
|
|
title={tt('5小时窗口')}
|
|
|
- windowData={primary}
|
|
|
|
|
|
|
+ windowData={fiveHourWindow}
|
|
|
/>
|
|
/>
|
|
|
<RateLimitWindowCard
|
|
<RateLimitWindowCard
|
|
|
t={tt}
|
|
t={tt}
|
|
|
title={tt('每周窗口')}
|
|
title={tt('每周窗口')}
|
|
|
- windowData={secondary}
|
|
|
|
|
|
|
+ windowData={weeklyWindow}
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|