diff --git a/web/src/components/table/channels/ChannelsColumnDefs.jsx b/web/src/components/table/channels/ChannelsColumnDefs.jsx index 7fdd71b0..5d748c0f 100644 --- a/web/src/components/table/channels/ChannelsColumnDefs.jsx +++ b/web/src/components/table/channels/ChannelsColumnDefs.jsx @@ -538,19 +538,24 @@ export const getChannelsColumns = ({ updateChannelBalance(record)} > - {renderQuotaWithAmount(record.balance)} + {record.type === 57 + ? t('帐号信息') + : renderQuotaWithAmount(record.balance)} diff --git a/web/src/components/table/channels/modals/CodexUsageModal.jsx b/web/src/components/table/channels/modals/CodexUsageModal.jsx index 76c84b90..1a42eafd 100644 --- a/web/src/components/table/channels/modals/CodexUsageModal.jsx +++ b/web/src/components/table/channels/modals/CodexUsageModal.jsx @@ -22,9 +22,11 @@ import { Modal, Button, Progress, - Tag, Typography, Spin, + Tag, + Descriptions, + Collapse, } from '@douyinfe/semi-ui'; import { API, showError } from '../../../../helpers'; @@ -128,6 +130,87 @@ const formatUnixSeconds = (unixSeconds) => { } }; +const getDisplayText = (value) => { + if (value == null) return ''; + return String(value).trim(); +}; + +const formatAccountTypeLabel = (value, t) => { + const tt = typeof t === 'function' ? t : (v) => v; + const normalized = normalizePlanType(value); + switch (normalized) { + case 'free': + return 'Free'; + case 'plus': + return 'Plus'; + case 'pro': + return 'Pro'; + case 'team': + return 'Team'; + case 'enterprise': + return 'Enterprise'; + default: + return getDisplayText(value) || tt('未识别'); + } +}; + +const getAccountTypeTagColor = (value) => { + const normalized = normalizePlanType(value); + switch (normalized) { + case 'enterprise': + return 'green'; + case 'team': + return 'cyan'; + case 'pro': + return 'blue'; + case 'plus': + return 'violet'; + case 'free': + return 'amber'; + default: + return 'grey'; + } +}; + +const resolveUsageStatusTag = (t, rateLimit) => { + const tt = typeof t === 'function' ? t : (v) => v; + if (!rateLimit || Object.keys(rateLimit).length === 0) { + return {tt('待确认')}; + } + if (rateLimit?.allowed && !rateLimit?.limit_reached) { + return {tt('可用')}; + } + return {tt('受限')}; +}; + +const AccountInfoValue = ({ t, value, onCopy, monospace = false }) => { + const tt = typeof t === 'function' ? t : (v) => v; + const text = getDisplayText(value); + const hasValue = text !== ''; + + return ( +
+
+ {hasValue ? text : '-'} +
+ +
+ ); +}; + const RateLimitWindowCard = ({ t, title, windowData }) => { const tt = typeof t === 'function' ? t : (v) => v; const hasWindowData = @@ -181,50 +264,100 @@ const RateLimitWindowCard = ({ t, title, windowData }) => { 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 allowed = !!rateLimit?.allowed; - const limitReached = !!rateLimit?.limit_reached; const upstreamStatus = payload?.upstream_status; - - const statusTag = - allowed && !limitReached ? ( - {tt('可用')} - ) : ( - {tt('受限')} - ); + const accountType = data?.plan_type ?? rateLimit?.plan_type; + const accountTypeLabel = formatAccountTypeLabel(accountType, tt); + const accountTypeTagColor = getAccountTypeTagColor(accountType); + const statusTag = resolveUsageStatusTag(tt, rateLimit); + const userId = data?.user_id; + const email = data?.email; + const accountId = data?.account_id; + const errorMessage = + payload?.success === false ? getDisplayText(payload?.message) || tt('获取用量失败') : ''; const rawText = typeof data === 'string' ? data : JSON.stringify(data ?? payload, null, 2); return ( -
-
- - {tt('渠道:')} - {record?.name || '-'} ({tt('编号:')} - {record?.id || '-'}) - -
- {statusTag} -
+ +
+ + + + + + + + + + + +
+ +
+ {tt('渠道:')} + {record?.name || '-'} ({tt('编号:')} + {record?.id || '-'}) +
-
- - {tt('上游状态码:')} - {upstreamStatus ?? '-'} - +
+
+
+ {tt('额度窗口')} +
+ + {tt('用于观察当前帐号在 Codex 上游的限额使用情况')} + +
@@ -240,23 +373,30 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => { />
-
-
-
{tt('原始 JSON')}
- -
-
-          {rawText}
-        
-
+ { + const keys = Array.isArray(activeKey) ? activeKey : [activeKey]; + setShowRawJson(keys.includes('raw-json')); + }} + > + +
+ +
+
+            {rawText}
+          
+
+
); }; @@ -351,7 +491,7 @@ export const openCodexUsageModal = ({ t, record, payload, onCopy }) => { const tt = typeof t === 'function' ? t : (v) => v; Modal.info({ - title: tt('Codex 用量'), + title: tt('Codex 帐号与用量'), centered: true, width: 900, style: { maxWidth: '95vw' },