Merge pull request #3399 from seefs001/refactor/codex-usage

Refactor/codex usage
This commit is contained in:
Calcium-Ion
2026-03-23 15:03:47 +08:00
committed by GitHub
2 changed files with 201 additions and 56 deletions

View File

@@ -538,19 +538,24 @@ export const getChannelsColumns = ({
</Tooltip> </Tooltip>
<Tooltip <Tooltip
content={ content={
t('剩余额度') + record.type === 57
': ' + ? t('查看 Codex 帐号信息与用量')
renderQuotaWithAmount(record.balance) + : t('剩余额度') +
t(',点击更新') ': ' +
renderQuotaWithAmount(record.balance) +
t(',点击更新')
} }
> >
<Tag <Tag
color='white' color={record.type === 57 ? 'light-blue' : 'white'}
type='ghost' type={record.type === 57 ? 'light' : 'ghost'}
shape='circle' shape='circle'
className={record.type === 57 ? 'cursor-pointer' : ''}
onClick={() => updateChannelBalance(record)} onClick={() => updateChannelBalance(record)}
> >
{renderQuotaWithAmount(record.balance)} {record.type === 57
? t('帐号信息')
: renderQuotaWithAmount(record.balance)}
</Tag> </Tag>
</Tooltip> </Tooltip>
</Space> </Space>

View File

@@ -22,9 +22,11 @@ import {
Modal, Modal,
Button, Button,
Progress, Progress,
Tag,
Typography, Typography,
Spin, Spin,
Tag,
Descriptions,
Collapse,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { API, showError } from '../../../../helpers'; 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 <Tag color='grey'>{tt('待确认')}</Tag>;
}
if (rateLimit?.allowed && !rateLimit?.limit_reached) {
return <Tag color='green'>{tt('可用')}</Tag>;
}
return <Tag color='red'>{tt('受限')}</Tag>;
};
const AccountInfoValue = ({ t, value, onCopy, monospace = false }) => {
const tt = typeof t === 'function' ? t : (v) => v;
const text = getDisplayText(value);
const hasValue = text !== '';
return (
<div className='flex min-w-0 items-start justify-between gap-2'>
<div
className={`min-w-0 flex-1 break-all text-xs leading-5 text-semi-color-text-1 ${
monospace ? 'font-mono' : ''
}`}
>
{hasValue ? text : '-'}
</div>
<Button
size='small'
type='tertiary'
theme='borderless'
className='shrink-0 px-1 text-xs'
disabled={!hasValue}
onClick={() => onCopy?.(text)}
>
{tt('复制')}
</Button>
</div>
);
};
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 = const hasWindowData =
@@ -181,50 +264,100 @@ const RateLimitWindowCard = ({ t, title, windowData }) => {
const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => { const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
const tt = typeof t === 'function' ? t : (v) => v; const tt = typeof t === 'function' ? t : (v) => v;
const [showRawJson, setShowRawJson] = useState(false);
const data = payload?.data ?? null; const data = payload?.data ?? null;
const rateLimit = data?.rate_limit ?? {}; const rateLimit = data?.rate_limit ?? {};
const { fiveHourWindow, weeklyWindow } = resolveRateLimitWindows(data); const { fiveHourWindow, weeklyWindow } = resolveRateLimitWindows(data);
const allowed = !!rateLimit?.allowed;
const limitReached = !!rateLimit?.limit_reached;
const upstreamStatus = payload?.upstream_status; const upstreamStatus = payload?.upstream_status;
const accountType = data?.plan_type ?? rateLimit?.plan_type;
const statusTag = const accountTypeLabel = formatAccountTypeLabel(accountType, tt);
allowed && !limitReached ? ( const accountTypeTagColor = getAccountTypeTagColor(accountType);
<Tag color='green'>{tt('可用')}</Tag> const statusTag = resolveUsageStatusTag(tt, rateLimit);
) : ( const userId = data?.user_id;
<Tag color='red'>{tt('受限')}</Tag> const email = data?.email;
); const accountId = data?.account_id;
const errorMessage =
payload?.success === false ? getDisplayText(payload?.message) || tt('获取用量失败') : '';
const rawText = const rawText =
typeof data === 'string' ? data : JSON.stringify(data ?? payload, null, 2); typeof data === 'string' ? data : JSON.stringify(data ?? payload, null, 2);
return ( return (
<div className='flex flex-col gap-3'> <div className='flex flex-col gap-4'>
<div className='flex flex-wrap items-center justify-between gap-2'> {errorMessage && (
<Text type='tertiary' size='small'> <div className='rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700'>
{tt('渠道:')} {errorMessage}
{record?.name || '-'} ({tt('编号:')} </div>
{record?.id || '-'}) )}
</Text>
<div className='flex items-center gap-2'> <div className='rounded-xl border border-semi-color-border bg-semi-color-bg-0 p-3'>
{statusTag} <div className='flex flex-wrap items-start justify-between gap-2'>
<Button <div className='min-w-0'>
size='small' <div className='text-xs font-medium text-semi-color-text-2'>
type='tertiary' {tt('Codex 帐号')}
theme='borderless' </div>
onClick={onRefresh} <div className='mt-2 flex flex-wrap items-center gap-2'>
> <Tag
color={accountTypeTagColor}
type='light'
shape='circle'
size='large'
className='font-semibold'
>
{accountTypeLabel}
</Tag>
{statusTag}
<Tag color='grey' type='light' shape='circle'>
{tt('上游状态码:')}
{upstreamStatus ?? '-'}
</Tag>
</div>
</div>
<Button size='small' type='tertiary' theme='outline' onClick={onRefresh}>
{tt('刷新')} {tt('刷新')}
</Button> </Button>
</div> </div>
<div className='mt-2 rounded-lg bg-semi-color-fill-0 px-3 py-2'>
<Descriptions>
<Descriptions.Item itemKey='User ID'>
<AccountInfoValue
t={tt}
value={userId}
onCopy={onCopy}
monospace={true}
/>
</Descriptions.Item>
<Descriptions.Item itemKey={tt('邮箱')}>
<AccountInfoValue t={tt} value={email} onCopy={onCopy} />
</Descriptions.Item>
<Descriptions.Item itemKey='Account ID'>
<AccountInfoValue
t={tt}
value={accountId}
onCopy={onCopy}
monospace={true}
/>
</Descriptions.Item>
</Descriptions>
</div>
<div className='mt-2 text-xs text-semi-color-text-2'>
{tt('渠道:')}
{record?.name || '-'} ({tt('编号:')}
{record?.id || '-'})
</div>
</div> </div>
<div className='flex flex-wrap items-center justify-between gap-2'> <div>
<Text type='tertiary' size='small'> <div className='mb-2'>
{tt('上游状态码:')} <div className='text-sm font-semibold text-semi-color-text-0'>
{upstreamStatus ?? '-'} {tt('额度窗口')}
</Text> </div>
<Text type='tertiary' size='small'>
{tt('用于观察当前帐号在 Codex 上游的限额使用情况')}
</Text>
</div>
</div> </div>
<div className='grid grid-cols-1 gap-3 md:grid-cols-2'> <div className='grid grid-cols-1 gap-3 md:grid-cols-2'>
@@ -240,23 +373,30 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
/> />
</div> </div>
<div> <Collapse
<div className='mb-1 flex items-center justify-between gap-2'> activeKey={showRawJson ? ['raw-json'] : []}
<div className='text-sm font-medium'>{tt('原始 JSON')}</div> onChange={(activeKey) => {
<Button const keys = Array.isArray(activeKey) ? activeKey : [activeKey];
size='small' setShowRawJson(keys.includes('raw-json'));
type='primary' }}
theme='outline' >
onClick={() => onCopy?.(rawText)} <Collapse.Panel header={tt('原始 JSON')} itemKey='raw-json'>
disabled={!rawText} <div className='mb-2 flex justify-end'>
> <Button
{tt('复制')} size='small'
</Button> type='primary'
</div> theme='outline'
<pre className='max-h-[50vh] overflow-auto rounded-lg bg-semi-color-fill-0 p-3 text-xs text-semi-color-text-0'> onClick={() => onCopy?.(rawText)}
{rawText} disabled={!rawText}
</pre> >
</div> {tt('复制')}
</Button>
</div>
<pre className='max-h-[50vh] overflow-y-auto rounded-lg bg-semi-color-fill-0 p-3 text-xs text-semi-color-text-0'>
{rawText}
</pre>
</Collapse.Panel>
</Collapse>
</div> </div>
); );
}; };
@@ -351,7 +491,7 @@ export const openCodexUsageModal = ({ t, record, payload, onCopy }) => {
const tt = typeof t === 'function' ? t : (v) => v; const tt = typeof t === 'function' ? t : (v) => v;
Modal.info({ Modal.info({
title: tt('Codex 用量'), title: tt('Codex 帐号与用量'),
centered: true, centered: true,
width: 900, width: 900,
style: { maxWidth: '95vw' }, style: { maxWidth: '95vw' },