mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-02 19:00:49 +08:00
136 lines
4.8 KiB
TypeScript
136 lines
4.8 KiB
TypeScript
/**
|
|
* Individual Gemini CLI quota card component.
|
|
*/
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import type {
|
|
GeminiCliQuotaState,
|
|
AuthFileItem,
|
|
ResolvedTheme,
|
|
ThemeColors
|
|
} from '@/types';
|
|
import { TYPE_COLORS, formatQuotaResetTime } from '@/utils/quota';
|
|
import styles from '@/pages/QuotaPage.module.scss';
|
|
|
|
interface GeminiCliCardProps {
|
|
item: AuthFileItem;
|
|
quota?: GeminiCliQuotaState;
|
|
resolvedTheme: ResolvedTheme;
|
|
getQuotaErrorMessage: (status: number | undefined, fallback: string) => string;
|
|
}
|
|
|
|
export function GeminiCliCard({
|
|
item,
|
|
quota,
|
|
resolvedTheme,
|
|
getQuotaErrorMessage
|
|
}: GeminiCliCardProps) {
|
|
const { t } = useTranslation();
|
|
|
|
const displayType = item.type || item.provider || 'gemini-cli';
|
|
const typeColorSet = TYPE_COLORS[displayType] || TYPE_COLORS.unknown;
|
|
const typeColor: ThemeColors =
|
|
resolvedTheme === 'dark' && typeColorSet.dark ? typeColorSet.dark : typeColorSet.light;
|
|
|
|
const quotaStatus = quota?.status ?? 'idle';
|
|
const buckets = quota?.buckets ?? [];
|
|
const quotaErrorMessage = getQuotaErrorMessage(
|
|
quota?.errorStatus,
|
|
quota?.error || t('common.unknown_error')
|
|
);
|
|
|
|
const getTypeLabel = (type: string): string => {
|
|
const key = `auth_files.filter_${type}`;
|
|
const translated = t(key);
|
|
if (translated !== key) return translated;
|
|
if (type.toLowerCase() === 'iflow') return 'iFlow';
|
|
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
};
|
|
|
|
return (
|
|
<div className={`${styles.fileCard} ${styles.geminiCliCard}`}>
|
|
<div className={styles.cardHeader}>
|
|
<span
|
|
className={styles.typeBadge}
|
|
style={{
|
|
backgroundColor: typeColor.bg,
|
|
color: typeColor.text,
|
|
...(typeColor.border ? { border: typeColor.border } : {})
|
|
}}
|
|
>
|
|
{getTypeLabel(displayType)}
|
|
</span>
|
|
<span className={styles.fileName}>{item.name}</span>
|
|
</div>
|
|
|
|
<div className={styles.quotaSection}>
|
|
{quotaStatus === 'loading' ? (
|
|
<div className={styles.quotaMessage}>{t('gemini_cli_quota.loading')}</div>
|
|
) : quotaStatus === 'idle' ? (
|
|
<div className={styles.quotaMessage}>{t('gemini_cli_quota.idle')}</div>
|
|
) : quotaStatus === 'error' ? (
|
|
<div className={styles.quotaError}>
|
|
{t('gemini_cli_quota.load_failed', {
|
|
message: quotaErrorMessage
|
|
})}
|
|
</div>
|
|
) : buckets.length === 0 ? (
|
|
<div className={styles.quotaMessage}>{t('gemini_cli_quota.empty_buckets')}</div>
|
|
) : (
|
|
buckets.map((bucket) => {
|
|
const fraction = bucket.remainingFraction;
|
|
const clamped = fraction === null ? null : Math.max(0, Math.min(1, fraction));
|
|
const percent = clamped === null ? null : Math.round(clamped * 100);
|
|
const percentLabel = percent === null ? '--' : `${percent}%`;
|
|
const resetLabel = formatQuotaResetTime(bucket.resetTime);
|
|
const remainingAmountLabel =
|
|
bucket.remainingAmount === null || bucket.remainingAmount === undefined
|
|
? null
|
|
: t('gemini_cli_quota.remaining_amount', {
|
|
count: bucket.remainingAmount
|
|
});
|
|
const titleBase =
|
|
bucket.modelIds && bucket.modelIds.length > 0
|
|
? bucket.modelIds.join(', ')
|
|
: bucket.label;
|
|
const quotaBarClass =
|
|
percent === null
|
|
? styles.quotaBarFillMedium
|
|
: percent >= 60
|
|
? styles.quotaBarFillHigh
|
|
: percent >= 20
|
|
? styles.quotaBarFillMedium
|
|
: styles.quotaBarFillLow;
|
|
|
|
return (
|
|
<div key={bucket.id} className={styles.quotaRow}>
|
|
<div className={styles.quotaRowHeader}>
|
|
<span
|
|
className={styles.quotaModel}
|
|
title={bucket.tokenType ? `${titleBase} (${bucket.tokenType})` : titleBase}
|
|
>
|
|
{bucket.label}
|
|
</span>
|
|
<div className={styles.quotaMeta}>
|
|
<span className={styles.quotaPercent}>{percentLabel}</span>
|
|
{remainingAmountLabel && (
|
|
<span className={styles.quotaAmount}>{remainingAmountLabel}</span>
|
|
)}
|
|
<span className={styles.quotaReset}>{resetLabel}</span>
|
|
</div>
|
|
</div>
|
|
<div className={styles.quotaBar}>
|
|
<div
|
|
className={`${styles.quotaBarFill} ${quotaBarClass}`}
|
|
style={{ width: `${percent ?? 0}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|