import { useEffect, useState } from 'react'; import { fetchModelGroups, fetchApiKeys, fetchGroupKeysDetail, fetchGroupPoolSummary, batchAddKeysToGroup, removeKeyFromGroup, updateGroupKey, testGroupKey, testAllGroupKeys } from '../api'; import type { ModelGroup, ApiKey, GroupKey, GroupPoolSummary } from '../api'; import './ApiKeys.css'; export function GroupKeys() { const [groups, setGroups] = useState([]); const [apiKeys, setApiKeys] = useState([]); const [selectedGroup, setSelectedGroup] = useState(null); const [groupKeys, setGroupKeys] = useState([]); const [poolSummary, setPoolSummary] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // 添加key弹窗 const [showAdd, setShowAdd] = useState(false); const [selectedKeyIds, setSelectedKeyIds] = useState([]); const [addError, setAddError] = useState(null); const [adding, setAdding] = useState(false); // 检测状态 const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState<{ total: number; active: number; failed: number; health_rate: number } | null>(null); const loadGroups = () => { setLoading(true); fetchModelGroups() .then(setGroups) .catch(() => setError('加载分组失败')) .finally(() => setLoading(false)); }; const loadApiKeys = () => { fetchApiKeys() .then(setApiKeys) .catch(() => console.error('加载API Keys失败')); }; useEffect(() => { loadGroups(); loadApiKeys(); }, []); const selectGroup = async (group: ModelGroup) => { setSelectedGroup(group); setTestResult(null); try { const [keys, summary] = await Promise.all([ fetchGroupKeysDetail(group.id), fetchGroupPoolSummary(group.id), ]); setGroupKeys(keys); setPoolSummary(summary); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }; const handleBatchAdd = async () => { if (!selectedGroup || selectedKeyIds.length === 0) { setAddError('请选择至少一个API Key'); return; } setAdding(true); setAddError(null); try { await batchAddKeysToGroup(selectedGroup.id, selectedKeyIds); setShowAdd(false); setSelectedKeyIds([]); selectGroup(selectedGroup); } catch (e) { setAddError(e instanceof Error ? e.message : String(e)); } finally { setAdding(false); } }; const handleRemove = async (id: number) => { if (!confirm('确认从号池中移除该Key?')) return; try { await removeKeyFromGroup(id); if (selectedGroup) selectGroup(selectedGroup); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }; const handleTestAll = async () => { if (!selectedGroup) return; setTesting(true); setTestResult(null); try { const result = await testAllGroupKeys(selectedGroup.id); setTestResult({ ...result, health_rate: result.total > 0 ? Math.round((result.active / result.total) * 100) : 0, }); selectGroup(selectedGroup); } catch (e) { setError(`检测失败: ${e instanceof Error ? e.message : String(e)}`); } finally { setTesting(false); } }; const handleTestKey = async (key: GroupKey) => { try { const result = await testGroupKey(key.id); setTestResult({ total: 1, active: result.success ? 1 : 0, failed: result.success ? 0 : 1, health_rate: result.success ? 100 : 0, }); if (selectedGroup) selectGroup(selectedGroup); } catch (e) { setError(`检测失败: ${e instanceof Error ? e.message : String(e)}`); } }; const handleToggleActive = async (key: GroupKey) => { try { await updateGroupKey(key.id, { is_active: !key.is_active }); if (selectedGroup) selectGroup(selectedGroup); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }; const getStatusBadge = (status: string) => { const styles: Record = { active: 'ak-status-active', rate_limited: 'ak-status-warning', failed: 'ak-status-error', banned: 'ak-status-banned', }; const labels: Record = { active: '正常', rate_limited: '限流', failed: '失败', banned: '禁用', }; return {labels[status] || status}; }; const getHealthColor = (rate: number) => { if (rate >= 80) return '#10b981'; if (rate >= 50) return '#f59e0b'; return '#ef4444'; }; const formatNumber = (num: number) => { if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; return num.toString(); }; // 已在当前分组的key ids const existingKeyIds = new Set(groupKeys.map(k => k.api_key_id)); return (
号池管理
为每个分组配置多个API Key,实现负载均衡和自动轮换
{error &&
{error}
} {testResult && (
= 50 ? '#78350f' : '#7f1d1d', color: '#f3f4f6', display: 'flex', gap: '24px', alignItems: 'center', }}> 检测结果 总计: {testResult.total} 正常: {testResult.active} 失败: {testResult.failed} = 50 ? '#f59e0b' : '#ef4444', fontWeight: 600}}>健康度: {testResult.health_rate}%
)}
{/* 左侧:分组列表 */}
分组列表
{groups.map(group => (
selectGroup(group)} >
{group.name}
{group.model_count} 个模型
))} {groups.length === 0 && !loading && (
暂无分组,请先在「模型分组」中创建
)}
{/* 右侧:号池详情 */}
{selectedGroup ? ( <> {/* 号池概览 */} {poolSummary && (
{selectedGroup.name} - 号池概览
总Key数
{poolSummary.total_keys}
正常
{poolSummary.active_keys}
限流
{poolSummary.rate_limited_keys}
失败
{poolSummary.failed_keys}
健康度
{poolSummary.health_rate}%
总Token
{formatNumber(poolSummary.total_tokens)}
)} {/* Key列表 */}
号池Key列表 ({groupKeys.length})
{groupKeys.length === 0 ? (
该分组暂无Key,点击上方按钮添加
) : (
{groupKeys.map(key => (
{key.api_key_name} {key.api_key_masked} {getStatusBadge(key.status)} {!key.is_active && 已禁用}
优先级: {key.priority} 权重: {key.weight} 请求: {formatNumber(key.request_count)} Token: {formatNumber(key.total_tokens)} 错误: {key.error_count} {key.last_used_at && ( 最后使用: {new Date(key.last_used_at).toLocaleString()} )}
{key.status_msg && (
状态说明: {key.status_msg}
)}
))}
)} ) : (
请选择一个分组查看号池
)}
{/* 添加Key弹窗 */} {showAdd && (
setShowAdd(false)}>
e.stopPropagation()}>
添加Key到号池
{addError &&
{addError}
}
{apiKeys .filter(k => !existingKeyIds.has(k.id)) .map(key => ( ))} {apiKeys.filter(k => !existingKeyIds.has(k.id)).length === 0 && (
所有Key都已在此号池中
)}
)}
); }