Files
Cli-Proxy-API-Management-Ce…/src/components/quota/GeminiCliSection/GeminiCliSection.tsx

183 lines
5.8 KiB
TypeScript

/**
* Gemini CLI quota section component.
*/
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { EmptyState } from '@/components/ui/EmptyState';
import { useQuotaStore, useThemeStore } from '@/stores';
import type { GeminiCliQuotaState, AuthFileItem, ResolvedTheme } from '@/types';
import { isGeminiCliFile, isRuntimeOnlyAuthFile } from '@/utils/quota';
import { useQuotaSection } from '../hooks/useQuotaSection';
import { useGeminiCliQuota } from './useGeminiCliQuota';
import { GeminiCliCard } from './GeminiCliCard';
import styles from '@/pages/QuotaPage.module.scss';
interface GeminiCliSectionProps {
files: AuthFileItem[];
loading: boolean;
disabled: boolean;
}
export function GeminiCliSection({ files, loading, disabled }: GeminiCliSectionProps) {
const { t } = useTranslation();
const resolvedTheme: ResolvedTheme = useThemeStore((state) => state.resolvedTheme);
const setGeminiCliQuota = useQuotaStore((state) => state.setGeminiCliQuota);
const geminiCliFiles = useMemo(
() => files.filter((file) => isGeminiCliFile(file) && !isRuntimeOnlyAuthFile(file)),
[files]
);
const {
pageSize,
totalPages,
currentPage,
pageItems,
setPageSize,
goToPrev,
goToNext,
loading: sectionLoading,
loadingScope,
setLoading
} = useQuotaSection({ items: geminiCliFiles });
const { quota, loadQuota } = useGeminiCliQuota();
const handleRefreshPage = useCallback(() => {
loadQuota(pageItems, 'page', setLoading);
}, [loadQuota, pageItems, setLoading]);
const handleRefreshAll = useCallback(() => {
loadQuota(geminiCliFiles, 'all', setLoading);
}, [loadQuota, geminiCliFiles, setLoading]);
const getQuotaErrorMessage = useCallback(
(status: number | undefined, fallback: string) => {
if (status === 404) return t('common.quota_update_required');
if (status === 403) return t('common.quota_check_credential');
return fallback;
},
[t]
);
// Sync quota state when files change
useEffect(() => {
if (loading) return;
if (geminiCliFiles.length === 0) {
setGeminiCliQuota({});
return;
}
setGeminiCliQuota((prev) => {
const nextState: Record<string, GeminiCliQuotaState> = {};
geminiCliFiles.forEach((file) => {
const cached = prev[file.name];
if (cached) {
nextState[file.name] = cached;
}
});
return nextState;
});
}, [geminiCliFiles, loading, setGeminiCliQuota]);
return (
<Card
title={t('gemini_cli_quota.title')}
extra={
<div className={styles.headerActions}>
<Button
variant="secondary"
size="sm"
onClick={handleRefreshPage}
disabled={disabled || sectionLoading || pageItems.length === 0}
loading={sectionLoading && loadingScope === 'page'}
>
{t('gemini_cli_quota.refresh_button')}
</Button>
<Button
variant="secondary"
size="sm"
onClick={handleRefreshAll}
disabled={disabled || sectionLoading || geminiCliFiles.length === 0}
loading={sectionLoading && loadingScope === 'all'}
>
{t('gemini_cli_quota.fetch_all')}
</Button>
</div>
}
>
{geminiCliFiles.length === 0 ? (
<EmptyState
title={t('gemini_cli_quota.empty_title')}
description={t('gemini_cli_quota.empty_desc')}
/>
) : (
<>
<div className={styles.geminiCliControls}>
<div className={styles.geminiCliControl}>
<label>{t('auth_files.page_size_label')}</label>
<select
className={styles.pageSizeSelect}
value={pageSize}
onChange={(e) => setPageSize(Number(e.target.value) || 6)}
>
<option value={6}>6</option>
<option value={9}>9</option>
<option value={12}>12</option>
<option value={18}>18</option>
<option value={24}>24</option>
</select>
</div>
<div className={styles.geminiCliControl}>
<label>{t('common.info')}</label>
<div className={styles.statsInfo}>
{geminiCliFiles.length} {t('auth_files.files_count')}
</div>
</div>
</div>
<div className={styles.geminiCliGrid}>
{pageItems.map((item) => (
<GeminiCliCard
key={item.name}
item={item}
quota={quota[item.name]}
resolvedTheme={resolvedTheme}
getQuotaErrorMessage={getQuotaErrorMessage}
/>
))}
</div>
{geminiCliFiles.length > pageSize && (
<div className={styles.pagination}>
<Button
variant="secondary"
size="sm"
onClick={goToPrev}
disabled={currentPage <= 1}
>
{t('auth_files.pagination_prev')}
</Button>
<div className={styles.pageInfo}>
{t('auth_files.pagination_info', {
current: currentPage,
total: totalPages,
count: geminiCliFiles.length
})}
</div>
<Button
variant="secondary"
size="sm"
onClick={goToNext}
disabled={currentPage >= totalPages}
>
{t('auth_files.pagination_next')}
</Button>
</div>
)}
</>
)}
</Card>
);
}