diff --git a/web/src/components/table/channels/modals/CodexUsageModal.jsx b/web/src/components/table/channels/modals/CodexUsageModal.jsx index 1a42eafd..3b5ad035 100644 --- a/web/src/components/table/channels/modals/CodexUsageModal.jsx +++ b/web/src/components/table/channels/modals/CodexUsageModal.jsx @@ -29,6 +29,7 @@ import { Collapse, } from '@douyinfe/semi-ui'; import { API, showError } from '../../../../helpers'; +import { MOBILE_BREAKPOINT } from '../../../../hooks/common/useIsMobile'; const { Text } = Typography; @@ -98,10 +99,12 @@ const resolveRateLimitWindows = (data) => { } if (!fiveHourWindow) { - fiveHourWindow = windows.find((windowData) => windowData !== weeklyWindow) ?? null; + fiveHourWindow = + windows.find((windowData) => windowData !== weeklyWindow) ?? null; } if (!weeklyWindow) { - weeklyWindow = windows.find((windowData) => windowData !== fiveHourWindow) ?? null; + weeklyWindow = + windows.find((windowData) => windowData !== fiveHourWindow) ?? null; } return { fiveHourWindow, weeklyWindow }; @@ -135,6 +138,40 @@ const getDisplayText = (value) => { return String(value).trim(); }; +const isMobileViewport = () => + typeof window !== 'undefined' && window.innerWidth < MOBILE_BREAKPOINT; + +const getCodexUsageModalLayout = () => { + if (isMobileViewport()) { + return { + width: 'calc(100vw - 16px)', + style: { + top: 8, + maxWidth: 'calc(100vw - 16px)', + margin: '0 auto', + }, + bodyStyle: { + maxHeight: 'calc(100vh - 148px)', + overflowY: 'auto', + padding: '16px 16px 12px', + }, + }; + } + + return { + width: 900, + style: { + top: 24, + maxWidth: 'min(900px, 92vw)', + }, + bodyStyle: { + maxHeight: 'calc(100vh - 172px)', + overflowY: 'auto', + padding: '20px 24px 16px', + }, + }; +}; + const formatAccountTypeLabel = (value, t) => { const tt = typeof t === 'function' ? t : (v) => v; const normalized = normalizePlanType(value); @@ -224,7 +261,7 @@ const RateLimitWindowCard = ({ t, title, windowData }) => { return (
-
+
{title}
{tt('重置时间:')} @@ -262,12 +299,86 @@ const RateLimitWindowCard = ({ t, title, windowData }) => { ); }; +const RateLimitWindowGrid = ({ t, fiveHourWindow, weeklyWindow }) => { + const tt = typeof t === 'function' ? t : (v) => v; + + return ( +
+ + +
+ ); +}; + +const RateLimitGroupSection = ({ + t, + title, + description, + rateLimitSource, + statusTag, + meteredFeature, +}) => { + const tt = typeof t === 'function' ? t : (v) => v; + const { fiveHourWindow, weeklyWindow } = + resolveRateLimitWindows(rateLimitSource); + const featureText = getDisplayText(meteredFeature); + + return ( +
+
+
+
+
+ {title} +
+ {statusTag} +
+ {(description || featureText) && ( +
+ {description ? {description} : null} + {featureText ? ( +
+ + metered_feature + + + {featureText} + +
+ ) : null} +
+ )} +
+
+ + +
+ ); +}; + const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => { const tt = typeof t === 'function' ? t : (v) => v; const [showRawJson, setShowRawJson] = useState(false); const data = payload?.data ?? null; const rateLimit = data?.rate_limit ?? {}; - const { fiveHourWindow, weeklyWindow } = resolveRateLimitWindows(data); + const additionalRateLimits = Array.isArray(data?.additional_rate_limits) + ? data.additional_rate_limits.filter( + (item) => + item && typeof item === 'object' && Object.keys(item).length > 0, + ) + : []; const upstreamStatus = payload?.upstream_status; const accountType = data?.plan_type ?? rateLimit?.plan_type; const accountTypeLabel = formatAccountTypeLabel(accountType, tt); @@ -277,7 +388,9 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => { const email = data?.email; const accountId = data?.account_id; const errorMessage = - payload?.success === false ? getDisplayText(payload?.message) || tt('获取用量失败') : ''; + payload?.success === false + ? getDisplayText(payload?.message) || tt('获取用量失败') + : ''; const rawText = typeof data === 'string' ? data : JSON.stringify(data ?? payload, null, 2); @@ -313,7 +426,12 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
-
@@ -355,22 +473,61 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => { {tt('额度窗口')} - {tt('用于观察当前帐号在 Codex 上游的限额使用情况')} + {tt( + '用于观察当前帐号在 Codex 上游的基础限额与附加计费能力使用情况', + )} -
- + - + + {additionalRateLimits.length > 0 ? ( +
+
+
+ {tt('附加额度')} +
+ + {tt('按模型或能力拆分的附加计费能力窗口')} + +
+ +
+ {additionalRateLimits.map((item, index) => { + const limitName = + getDisplayText(item?.limit_name) || + getDisplayText(item?.metered_feature) || + `${tt('附加额度')} ${index + 1}`; + + return ( +
0 ? 'border-t border-semi-color-border pt-4' : '' + } + > + +
+ ); + })} +
+
+ ) : null}
{ export const openCodexUsageModal = ({ t, record, payload, onCopy }) => { const tt = typeof t === 'function' ? t : (v) => v; + const layout = getCodexUsageModalLayout(); Modal.info({ title: tt('Codex 帐号与用量'), - centered: true, - width: 900, - style: { maxWidth: '95vw' }, + centered: false, + width: layout.width, + style: layout.style, + bodyStyle: layout.bodyStyle, content: (