import { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useConfirmation } from '../components/ConfirmationDialog'; import { QuotaActionFeedback } from '../components/QuotaActionFeedback'; import { AuthFileQuotaPanel } from '../components/AuthFileQuotaPanel'; import { AuthFileSettingsDialog } from '../components/AuthFileSettingsDialog'; import { useCodexQuotaReset } from '../components/useCodexQuotaReset'; import './AuthFileManagementPage.css'; import { MessageNotice, FloatingNotice, useAppNotice } from '../appNotice'; import { AuthFileModelsDialog } from '../components/AuthFileModelsDialog'; import { AuthFileRequestStatus } from '../components/AuthFileRequestStatus'; import { AuthFileHealthStatus } from '../components/AuthFileHealthStatus'; import { Check, Copy, FileDown, FolderOpen, Import, LoaderCircle, RefreshCw, Search, Settings2, Trash2, X, } from 'lucide-react'; import antigravityIcon from '../assets/icons/antigravity.svg'; import claudeIcon from '../assets/icons/claude.svg'; import codexIcon from '../assets/icons/codex.svg'; import geminiIcon from '../assets/icons/gemini.svg'; import grokIcon from '../assets/icons/grok.svg'; import devinIcon from '../assets/icons/devin.svg'; import kimiIcon from '../assets/icons/kimi-light.svg'; import vertexIcon from '../assets/icons/vertex.svg'; import { formatDate, managementApi, readBoolean, readNumber, readString, responseList, } from '../services/managementApi'; import { idleQuota, loadQuota, providerForFile as quotaProviderForFile, quotaKey, } from '../services/quotaService'; import { captureQuotaCacheGeneration, commitQuotaCacheIfCurrent, getQuotaCacheSnapshot, pruneQuotaCache, updateQuotaCache, useQuotaCache, } from '../services/quotaCache'; import { authFileName, dedupeAuthFiles, isRuntimeOnlyAuthFile, parseAuthFilePriority, } from '../services/authFiles'; import { modelMatchesRule, normalizeOAuthExcludedRules, openOAuthModelNames, setOAuthModelsExcluded, type OAuthModelDefinition, } from '../services/oauthModels'; import { loadOAuthModelSettings, saveOAuthModelSettings, type OAuthModelSettings, type OAuthModelTarget, } from '../services/oauthModelSettings'; import { getCurrentLocale, translate, useI18n } from '../i18n'; type AuthFile = Record; const providerIcons: Record = { antigravity: antigravityIcon, claude: claudeIcon, codex: codexIcon, gemini: geminiIcon, kimi: kimiIcon, vertex: vertexIcon, xai: grokIcon, devin: devinIcon, }; const providerName = (file: AuthFile) => { const value = readString(file, 'provider', 'type', 'account_type').toLowerCase(); if (value === 'anthropic') return 'Claude'; if (value === 'anti-gravity') return 'Antigravity'; if (value === 'xai') return 'xAI'; if (value === 'cognition') return 'Devin'; return value ? value.charAt(0).toUpperCase() + value.slice(1) : translate(getCurrentLocale(), 'authFiles.unknownProvider'); }; const providerKey = (file: AuthFile) => { const value = readString(file, 'provider', 'type', 'account_type').toLowerCase(); if (value === 'cognition') return 'devin'; return value === 'anthropic' ? 'claude' : value === 'anti-gravity' ? 'antigravity' : value; }; const fileName = authFileName; const isRuntimeOnly = isRuntimeOnlyAuthFile; export function AuthFileManagementPage() { const { t } = useI18n(); const { askConfirmation, confirmationDialog } = useConfirmation(); const [fileSnapshot, setFileSnapshot] = useState<{ files: AuthFile[]; receivedAtMs: number; observedAt?: string; }>({ files: [], receivedAtMs: 0 }); const { files, receivedAtMs, observedAt } = fileSnapshot; const [filter, setFilter] = useState(''); const [providerFilter, setProviderFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState<'all' | 'enabled' | 'disabled' | 'runtime'>('all'); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const resetCodexQuota = useCodexQuotaReset(askConfirmation, setError); const feedback = useAppNotice(); const { showNotice } = feedback; const [copied, setCopied] = useState(''); const [settingsName, setSettingsName] = useState(null); const [oauthModelTarget, setOauthModelTarget] = useState(null); const [oauthModelSettings, setOauthModelSettings] = useState(null); const [oauthExcludedRulesText, setOauthExcludedRulesText] = useState(''); const [modelViewName, setModelViewName] = useState(null); const [oauthModelSearch, setOauthModelSearch] = useState(''); const [oauthModelLoading, setOauthModelLoading] = useState(false); const [oauthModelSaving, setOauthModelSaving] = useState(false); const [oauthModelError, setOauthModelError] = useState(''); const quotas = useQuotaCache(); const fileInputRef = useRef(null); const oauthModelRequestRef = useRef(0); const oauthModelSaveRef = useRef(false); const oauthModels = oauthModelSettings?.models ?? []; const oauthExcludedRules = normalizeOAuthExcludedRules(oauthExcludedRulesText.split(/\r?\n/)); const excludedOauthModelCount = oauthModels.length - openOAuthModelNames(oauthModels, oauthExcludedRules).size; const oauthModelProviders = useMemo(() => Array.from(new Map( files.filter((file) => providerKey(file)).map((file) => [providerKey(file), { provider: providerKey(file), label: providerName(file), }]), ).values()).sort((a, b) => a.label.localeCompare(b.label)), [files]); const loadFiles = useCallback(async (showLoading = true) => { if (showLoading) setLoading(true); setError(''); try { const payload = await managementApi.get('/auth-files'); const nextFiles = dedupeAuthFiles(responseList(payload, 'files')); setFileSnapshot({ files: nextFiles, receivedAtMs: Date.now(), observedAt: readString(payload, 'observed_at') }); const validQuotaKeys = new Set(nextFiles.map(quotaKey)); pruneQuotaCache(validQuotaKeys); updateQuotaCache((current) => { const next = { ...current }; nextFiles.forEach((file) => { const key = quotaKey(file); if (!next[key]) next[key] = idleQuota(); }); return next; }); } catch (requestError) { setError(String(requestError)); } finally { if (showLoading) setLoading(false); } }, []); const refreshQuota = async (file: AuthFile) => { if (readBoolean(file, 'disabled')) return; const key = quotaKey(file); if (getQuotaCacheSnapshot()[key]?.status === 'loading') return; const cacheGeneration = captureQuotaCacheGeneration(); updateQuotaCache((current) => ({ ...current, [key]: { status: 'loading', rows: [] } })); const result = await loadQuota(file); commitQuotaCacheIfCurrent(cacheGeneration, () => { updateQuotaCache((current) => ({ ...current, [key]: result })); }); }; const closeOauthModels = () => { if (oauthModelSaveRef.current) return; oauthModelRequestRef.current += 1; setOauthModelTarget(null); setOauthModelSettings(null); }; const openOauthModelSettings = async (target: OAuthModelTarget) => { if (oauthModelSaveRef.current) return; const requestId = oauthModelRequestRef.current + 1; oauthModelRequestRef.current = requestId; setOauthModelTarget(target); setOauthModelSettings(null); setOauthExcludedRulesText(''); setOauthModelSearch(''); setOauthModelError(''); setOauthModelLoading(true); try { const settings = await loadOAuthModelSettings(target); if (oauthModelRequestRef.current !== requestId) return; setOauthModelSettings(settings); setOauthExcludedRulesText(settings.excludedRules.join('\n')); } catch (requestError) { if (oauthModelRequestRef.current === requestId) setOauthModelError(String(requestError)); } finally { if (oauthModelRequestRef.current === requestId) setOauthModelLoading(false); } }; const saveOauthModels = async () => { if (!oauthModelSettings || oauthModelLoading || oauthModelSaveRef.current) return; const settings = oauthModelSettings; oauthModelSaveRef.current = true; setOauthModelSaving(true); setOauthModelError(''); try { await saveOAuthModelSettings(settings, oauthExcludedRules); showNotice(settings.target.scope === 'credential' ? { key: 'authFiles.models.credentialUpdated', variables: { name: settings.target.name } } : { key: 'authFiles.models.updated', variables: { provider: settings.target.label } }); oauthModelSaveRef.current = false; closeOauthModels(); if (settings.target.scope === 'credential') void loadFiles(false); } catch (requestError) { setOauthModelError(String(requestError)); } finally { oauthModelSaveRef.current = false; setOauthModelSaving(false); } }; const visibleOauthModels = useMemo(() => { const query = oauthModelSearch.trim().toLowerCase(); if (!query) return oauthModels; return oauthModels.filter((model) => `${model.id} ${model.displayName ?? ''}`.toLowerCase().includes(query), ); }, [oauthModelSearch, oauthModels]); const setOauthModelsExcluded = (models: OAuthModelDefinition[], excluded: boolean) => { if (oauthModelSaveRef.current) return; setOauthExcludedRulesText((current) => setOAuthModelsExcluded(current.split(/\r?\n/), models, excluded).join('\n'), ); }; useEffect(() => { void loadFiles(); }, [loadFiles]); const providers = useMemo( () => Array.from(new Set(files.map(providerName))).sort((left, right) => left.localeCompare(right)), [files], ); const visibleFiles = useMemo(() => { const query = filter.trim().toLowerCase(); return files.filter((file) => { const providerMatch = providerFilter === 'all' || providerName(file) === providerFilter; const disabled = readBoolean(file, 'disabled'); const runtimeMatch = statusFilter === 'all' || (statusFilter === 'disabled' && disabled) || (statusFilter === 'enabled' && !disabled) || (statusFilter === 'runtime' && isRuntimeOnly(file)); const searchMatch = !query || [fileName(file), providerName(file), readString(file, 'email', 'account', 'label')] .join(' ') .toLowerCase() .includes(query); return providerMatch && runtimeMatch && searchMatch; }); }, [files, filter, providerFilter, statusFilter]); const handleUpload = async (event: ChangeEvent) => { const selected = Array.from(event.currentTarget.files ?? []); event.currentTarget.value = ''; if (selected.length === 0) return; setBusy(true); setError(''); let uploaded = 0; const failures: string[] = []; for (const file of selected) { try { await managementApi.uploadAuthFile(file); uploaded += 1; } catch (requestError) { failures.push(`${file.name}:${String(requestError)}`); } } try { await loadFiles(); if (uploaded > 0) showNotice({ key: 'authFiles.uploaded', variables: { count: uploaded } }); if (failures.length > 0) setError(t('authFiles.uploadFailed', { count: failures.length, errors: failures.join('; ') })); } catch (requestError) { setError(String(requestError)); } finally { setBusy(false); } }; const toggleStatus = async (file: AuthFile) => { const name = fileName(file); feedback.clearNotice(); setBusy(true); setError(''); try { await managementApi.patch('/auth-files/status', { name, disabled: !readBoolean(file, 'disabled'), }); await loadFiles(false); } catch (requestError) { setError(String(requestError)); } finally { setBusy(false); } }; const deleteFile = async (file: AuthFile) => { const name = fileName(file); if (isRuntimeOnly(file)) { setError(t('authFiles.runtimeDeleteError')); return; } if (!await askConfirmation({ title: t('common.delete'), message: t('authFiles.deleteConfirm', { name }), confirmText: t('common.delete'), variant: 'danger' })) return; setBusy(true); setError(''); try { await managementApi.delete('/auth-files', { query: { name } }); showNotice({ key: 'authFiles.deleted' }); await loadFiles(); } catch (requestError) { setError(String(requestError)); } finally { setBusy(false); } }; const openAuthFilesDirectory = async () => { setBusy(true); setError(''); try { await managementApi.openAuthFilesDirectory(); } catch (requestError) { setError(String(requestError)); } finally { setBusy(false); } }; const copyName = async (name: string) => { try { await navigator.clipboard.writeText(name); setCopied(name); window.setTimeout(() => setCopied((current) => (current === name ? '' : current)), 1500); } catch { setError(t('common.copyFailed')); } }; const disabledCount = files.filter((file) => readBoolean(file, 'disabled')).length; const runtimeCount = files.filter(isRuntimeOnly).length; return (
{confirmationDialog}

{t('authFiles.title')}

{t('authFiles.summary', { files: files.length, disabled: disabledCount })} void handleUpload(event)} />
{error ? setError('')} /> : null}
setFilter(event.currentTarget.value)} placeholder={t('authFiles.searchPlaceholder')} />
{loading ? (
{t('authFiles.loading')}
) : visibleFiles.length === 0 ? (
{files.length ? t('authFiles.empty.filtered') : t('authFiles.empty.none')}{files.length ? t('authFiles.empty.tryFilter') : t('authFiles.empty.upload')}
) : (
{visibleFiles.map((file) => { const name = fileName(file); const icon = providerIcons[providerKey(file)] ?? geminiIcon; const disabled = readBoolean(file, 'disabled'); const priority = parseAuthFilePriority(file.priority) ?? 0; const identity = readString(file, 'email', 'project_id', 'label'); const note = readString(file, 'note'); const quota = quotas[quotaKey(file)] ?? idleQuota(); return (
{providerName(file)}{identity || name}
{isRuntimeOnly(file) ? {t('authFiles.runtime')} : null}
{identity ?

{name}

: null} {quotaProviderForFile(file) ? void refreshQuota(file)} onReset={quotaProviderForFile(file) === 'codex' ? () => void resetCodexQuota(file, quota) : undefined} /> : null}
{t('authFiles.priority.button', { priority })} · {readNumber(file, 'size') === null ? t('authFiles.unknownSize') : `${Math.ceil((readNumber(file, 'size') ?? 0) / 1024)} KB`} {formatDate(file.modtime ?? file.updated_at ?? file.last_refresh)}
{note ?
{t('authFiles.settings.note')}

{note}

: null}
{providerKey(file) ? : null}
); })}
)}
{runtimeCount > 0 ?

{t('authFiles.runtimeFootnote', { count: runtimeCount })}

: null} {settingsName ? setSettingsName(null)} onSaved={() => { showNotice({ key: 'authFiles.settings.updated', variables: { name: settingsName } }); setSettingsName(null); void loadFiles(false); }} /> : null} {modelViewName ? setModelViewName(null)} /> : null} {oauthModelTarget ? (
event.currentTarget === event.target && !oauthModelSaving && closeOauthModels()}>
{ if (event.key === 'Escape') closeOauthModels(); }}>

{t(oauthModelTarget.scope === 'credential' ? 'authFiles.models.title' : 'authFiles.models.globalButton')}

{oauthModelTarget.scope === 'credential' ? ( {oauthModelTarget.name} ) : ( )} {t(oauthModelTarget.scope === 'credential' ? 'authFiles.models.description' : 'authFiles.models.globalDescription', { provider: oauthModelTarget.label })}
{t('authFiles.models.summary', { total: oauthModels.length, excluded: excludedOauthModelCount })}
{oauthModelLoading ? (
{t('authFiles.models.loading')}
) : oauthModelError && !oauthModelSettings ? (
{t('authFiles.models.loadFailed')}{oauthModelError}
) : (
{oauthModelError ? setOauthModelError('')} /> : null} {oauthModelSettings?.catalogError ? : null}
{visibleOauthModels.length === 0 ? (
{oauthModels.length ? t('authFiles.models.noMatch') : t('authFiles.models.empty')}
) : (
{visibleOauthModels.map((model) => { const wildcardRule = oauthExcludedRules.find((rule) => rule.includes('*') && modelMatchesRule(model.id, rule)); const checked = oauthExcludedRules.some((rule) => modelMatchesRule(model.id, rule)); return ( ); })}
)}
)}