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: (