import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; import { SelectionCheckbox } from '@/components/ui/SelectionCheckbox'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { IconDownload, IconInfo, IconModelCluster, IconSettings, IconTrash2, } from '@/components/ui/icons'; import { ProviderStatusBar } from '@/components/providers/ProviderStatusBar'; import type { AuthFileItem } from '@/types'; import { resolveAuthProvider } from '@/utils/quota'; import { calculateStatusBarData, normalizeAuthIndex, type KeyStats } from '@/utils/usage'; import { formatFileSize } from '@/utils/format'; import { QUOTA_PROVIDER_TYPES, formatModified, getAuthFileIcon, getAuthFileStatusMessage, getTypeColor, getTypeLabel, isRuntimeOnlyAuthFile, parsePriorityValue, resolveAuthFileStats, type QuotaProviderType, type ResolvedTheme, } from '@/features/authFiles/constants'; import type { AuthFileStatusBarData } from '@/features/authFiles/hooks/useAuthFilesStatusBarCache'; import { AuthFileQuotaSection } from '@/features/authFiles/components/AuthFileQuotaSection'; import styles from '@/pages/AuthFilesPage.module.scss'; const HEALTHY_STATUS_MESSAGES = new Set(['ok', 'healthy', 'ready', 'success', 'available']); export type AuthFileCardProps = { file: AuthFileItem; compact: boolean; selected: boolean; resolvedTheme: ResolvedTheme; disableControls: boolean; deleting: string | null; statusUpdating: Record; quotaFilterType: QuotaProviderType | null; keyStats: KeyStats; statusBarCache: Map; onShowModels: (file: AuthFileItem) => void; onDownload: (name: string) => void; onOpenPrefixProxyEditor: (file: AuthFileItem) => void; onDelete: (name: string) => void; onToggleStatus: (file: AuthFileItem, enabled: boolean) => void; onToggleSelect: (name: string) => void; }; const resolveQuotaType = (file: AuthFileItem): QuotaProviderType | null => { const provider = resolveAuthProvider(file); if (!QUOTA_PROVIDER_TYPES.has(provider as QuotaProviderType)) return null; return provider as QuotaProviderType; }; export function AuthFileCard(props: AuthFileCardProps) { const { t } = useTranslation(); const { file, compact, selected, resolvedTheme, disableControls, deleting, statusUpdating, quotaFilterType, keyStats, statusBarCache, onShowModels, onDownload, onOpenPrefixProxyEditor, onDelete, onToggleStatus, onToggleSelect, } = props; const fileStats = resolveAuthFileStats(file, keyStats); const isRuntimeOnly = isRuntimeOnlyAuthFile(file); const isAistudio = (file.type || '').toLowerCase() === 'aistudio'; const showModelsButton = !isRuntimeOnly || isAistudio; const typeColor = getTypeColor(file.type || 'unknown', resolvedTheme); const typeLabel = getTypeLabel(t, file.type || 'unknown'); const providerIcon = getAuthFileIcon(file.type || 'unknown', resolvedTheme); const quotaType = quotaFilterType && resolveQuotaType(file) === quotaFilterType ? quotaFilterType : null; const showQuotaLayout = Boolean(quotaType) && !isRuntimeOnly && !compact; const providerCardClass = quotaType === 'antigravity' ? styles.antigravityCard : quotaType === 'claude' ? styles.claudeCard : quotaType === 'codex' ? styles.codexCard : quotaType === 'gemini-cli' ? styles.geminiCliCard : quotaType === 'kimi' ? styles.kimiCard : ''; const rawAuthIndex = file['auth_index'] ?? file.authIndex; const authIndexKey = normalizeAuthIndex(rawAuthIndex); const statusData = (authIndexKey && statusBarCache.get(authIndexKey)) || calculateStatusBarData([]); const rawStatusMessage = getAuthFileStatusMessage(file); const hasStatusWarning = Boolean(rawStatusMessage) && !HEALTHY_STATUS_MESSAGES.has(rawStatusMessage.toLowerCase()); const priorityValue = parsePriorityValue(file.priority ?? file['priority']); const noteValue = typeof file.note === 'string' ? file.note.trim() : ''; const stateLabel = isRuntimeOnly ? t('auth_files.type_virtual') || '虚拟认证文件' : file.disabled ? t('auth_files.health_status_disabled') : hasStatusWarning ? t('auth_files.health_status_warning') : rawStatusMessage ? t('auth_files.health_status_healthy') : t('auth_files.status_toggle_label'); const stateBadgeClass = isRuntimeOnly ? styles.stateBadgeVirtual : file.disabled ? styles.stateBadgeDisabled : hasStatusWarning ? styles.stateBadgeWarning : styles.stateBadgeActive; return (
{!isRuntimeOnly && ( onToggleSelect(file.name)} className={styles.cardSelection} aria-label={ selected ? t('auth_files.batch_deselect') : t('auth_files.batch_select_all') } title={selected ? t('auth_files.batch_deselect') : t('auth_files.batch_select_all')} /> )}
{providerIcon ? ( ) : ( {typeLabel.slice(0, 1).toUpperCase()} )}
{typeLabel} {stateLabel}
{file.name} {!compact && noteValue && (
{t('auth_files.note_display')} {noteValue}
)}
{t('auth_files.file_size')} {file.size ? formatFileSize(file.size) : '-'}
{t('auth_files.file_modified')} {formatModified(file)}
{priorityValue !== undefined && (
{t('auth_files.priority_display')} {priorityValue}
)}
{rawStatusMessage && hasStatusWarning && (
{rawStatusMessage}
)}
{t('stats.success')} {fileStats.success}
{t('stats.failure')} {fileStats.failure}
{t('auth_files.health_status_label')}
{showQuotaLayout && quotaType && ( )}
{showModelsButton && ( )} {!isRuntimeOnly && (
)}
{!isRuntimeOnly && (
{t('auth_files.status_toggle_label')} onToggleStatus(file, value)} />
)}
); }