|
|
@@ -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 (
|
|
|
<div className='rounded-lg border border-semi-color-border bg-semi-color-bg-0 p-3'>
|
|
|
- <div className='flex items-center justify-between gap-2'>
|
|
|
+ <div className='flex flex-wrap items-start justify-between gap-x-3 gap-y-1'>
|
|
|
<div className='font-medium'>{title}</div>
|
|
|
<Text type='tertiary' size='small'>
|
|
|
{tt('重置时间:')}
|
|
|
@@ -262,12 +299,86 @@ const RateLimitWindowCard = ({ t, title, windowData }) => {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+const RateLimitWindowGrid = ({ t, fiveHourWindow, weeklyWindow }) => {
|
|
|
+ const tt = typeof t === 'function' ? t : (v) => v;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className='grid grid-cols-1 gap-3 md:grid-cols-2'>
|
|
|
+ <RateLimitWindowCard
|
|
|
+ t={tt}
|
|
|
+ title={tt('5小时窗口')}
|
|
|
+ windowData={fiveHourWindow}
|
|
|
+ />
|
|
|
+ <RateLimitWindowCard
|
|
|
+ t={tt}
|
|
|
+ title={tt('每周窗口')}
|
|
|
+ windowData={weeklyWindow}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+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 (
|
|
|
+ <section className='space-y-3'>
|
|
|
+ <div className='flex flex-wrap items-start justify-between gap-3'>
|
|
|
+ <div className='min-w-0 space-y-2'>
|
|
|
+ <div className='flex flex-wrap items-center gap-2'>
|
|
|
+ <div className='text-sm font-semibold text-semi-color-text-0'>
|
|
|
+ {title}
|
|
|
+ </div>
|
|
|
+ {statusTag}
|
|
|
+ </div>
|
|
|
+ {(description || featureText) && (
|
|
|
+ <div className='flex flex-wrap items-center gap-2 text-xs text-semi-color-text-2'>
|
|
|
+ {description ? <span>{description}</span> : null}
|
|
|
+ {featureText ? (
|
|
|
+ <div className='inline-flex max-w-full items-center gap-2 rounded-full bg-semi-color-fill-0 px-2 py-1'>
|
|
|
+ <span className='text-[11px] text-semi-color-text-2'>
|
|
|
+ metered_feature
|
|
|
+ </span>
|
|
|
+ <span className='min-w-0 break-all font-mono text-xs text-semi-color-text-0'>
|
|
|
+ {featureText}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <RateLimitWindowGrid
|
|
|
+ t={tt}
|
|
|
+ fiveHourWindow={fiveHourWindow}
|
|
|
+ weeklyWindow={weeklyWindow}
|
|
|
+ />
|
|
|
+ </section>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
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 }) => {
|
|
|
</Tag>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <Button size='small' type='tertiary' theme='outline' onClick={onRefresh}>
|
|
|
+ <Button
|
|
|
+ size='small'
|
|
|
+ type='tertiary'
|
|
|
+ theme='outline'
|
|
|
+ onClick={onRefresh}
|
|
|
+ >
|
|
|
{tt('刷新')}
|
|
|
</Button>
|
|
|
</div>
|
|
|
@@ -355,22 +473,61 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
|
|
|
{tt('额度窗口')}
|
|
|
</div>
|
|
|
<Text type='tertiary' size='small'>
|
|
|
- {tt('用于观察当前帐号在 Codex 上游的限额使用情况')}
|
|
|
+ {tt(
|
|
|
+ '用于观察当前帐号在 Codex 上游的基础限额与附加计费能力使用情况',
|
|
|
+ )}
|
|
|
</Text>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div className='grid grid-cols-1 gap-3 md:grid-cols-2'>
|
|
|
- <RateLimitWindowCard
|
|
|
+ <div className='space-y-5'>
|
|
|
+ <RateLimitGroupSection
|
|
|
t={tt}
|
|
|
- title={tt('5小时窗口')}
|
|
|
- windowData={fiveHourWindow}
|
|
|
- />
|
|
|
- <RateLimitWindowCard
|
|
|
- t={tt}
|
|
|
- title={tt('每周窗口')}
|
|
|
- windowData={weeklyWindow}
|
|
|
+ title={tt('基础额度')}
|
|
|
+ description={tt('当前帐号的基础额度窗口')}
|
|
|
+ rateLimitSource={data}
|
|
|
+ statusTag={statusTag}
|
|
|
/>
|
|
|
+
|
|
|
+ {additionalRateLimits.length > 0 ? (
|
|
|
+ <div className='space-y-4 border-t border-semi-color-border pt-4'>
|
|
|
+ <div>
|
|
|
+ <div className='text-sm font-semibold text-semi-color-text-0'>
|
|
|
+ {tt('附加额度')}
|
|
|
+ </div>
|
|
|
+ <Text type='tertiary' size='small'>
|
|
|
+ {tt('按模型或能力拆分的附加计费能力窗口')}
|
|
|
+ </Text>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className='space-y-4'>
|
|
|
+ {additionalRateLimits.map((item, index) => {
|
|
|
+ const limitName =
|
|
|
+ getDisplayText(item?.limit_name) ||
|
|
|
+ getDisplayText(item?.metered_feature) ||
|
|
|
+ `${tt('附加额度')} ${index + 1}`;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ key={`${limitName}-${getDisplayText(item?.metered_feature)}-${index}`}
|
|
|
+ className={
|
|
|
+ index > 0 ? 'border-t border-semi-color-border pt-4' : ''
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <RateLimitGroupSection
|
|
|
+ t={tt}
|
|
|
+ title={limitName}
|
|
|
+ description={tt('附加计费能力')}
|
|
|
+ rateLimitSource={item}
|
|
|
+ statusTag={resolveUsageStatusTag(tt, item?.rate_limit)}
|
|
|
+ meteredFeature={item?.metered_feature}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
</div>
|
|
|
|
|
|
<Collapse
|
|
|
@@ -489,12 +646,14 @@ const CodexUsageLoader = ({ t, record, initialPayload, onCopy }) => {
|
|
|
|
|
|
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: (
|
|
|
<CodexUsageLoader
|
|
|
t={tt}
|