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..2e36474a 100644 --- a/web/src/components/table/channels/modals/CodexUsageModal.jsx +++ b/web/src/components/table/channels/modals/CodexUsageModal.jsx @@ -22,9 +22,9 @@ import { Modal, Button, Progress, - Tag, Typography, Spin, + Tag, } from '@douyinfe/semi-ui'; import { API, showError } from '../../../../helpers'; @@ -128,6 +128,91 @@ 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 AccountInfoCard = ({ t, label, value, onCopy, monospace = false }) => { + const tt = typeof t === 'function' ? t : (v) => v; + const text = getDisplayText(value); + const hasValue = text !== ''; + + return ( +
+
+ {label} +
+
+
+ {hasValue ? text : '-'} +
+ +
+
+ ); +}; + const RateLimitWindowCard = ({ t, title, windowData }) => { const tt = typeof t === 'function' ? t : (v) => v; const hasWindowData = @@ -184,47 +269,92 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => { 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 ( -
-
- +
+ {errorMessage && ( +
+ {errorMessage} +
+ )} + +
+
+
+
+
+ {tt('Codex 帐号')} +
+
+ + {accountTypeLabel} + + {statusTag} +
+ {tt('上游状态码:')} + {upstreamStatus ?? '-'} +
+
+
+ +
+
+ +
+ + + +
+ +
{tt('渠道:')} {record?.name || '-'} ({tt('编号:')} {record?.id || '-'}) - -
- {statusTag} -
-
- - {tt('上游状态码:')} - {upstreamStatus ?? '-'} - +
+
+
+ {tt('额度窗口')} +
+ + {tt('用于观察当前帐号在 Codex 上游的限额使用情况')} + +
@@ -351,7 +481,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' },