import React, { useState, useEffect } from 'react'; import { api } from '../../services/api'; import { SystemConfig, OpenRouterModelConfig, DoubaoModelConfig } from '../../types'; import { Bot, CheckCircle, X, BarChart2, Brain, Activity, ShieldCheck, Power, Key, Trash2, Plus, Save, Layers, ArrowUp, ArrowDown, Server } from 'lucide-react'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from 'recharts'; import { Toast, ToastState } from '../Toast'; const PIE_COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#06b6d4']; const DEFAULT_OR_MODELS = [ { id: 'qwen/qwen3-coder:free', name: 'Qwen 3 Coder', isCustom: false }, { id: 'openai/gpt-oss-120b:free', name: 'GPT OSS 120B', isCustom: false }, { id: 'qwen/qwen3-235b-a22b:free', name: 'Qwen 3 235B', isCustom: false }, { id: 'tngtech/deepseek-r1t-chimera:free', name: 'DeepSeek R1T', isCustom: false } ]; const ALL_PROVIDERS = ['GEMINI', 'OPENROUTER', 'DOUBAO', 'GEMMA']; export const AdminPanel: React.FC = () => { const [systemConfig, setSystemConfig] = useState(null); const [toast, setToast] = useState({ show: false, message: '', type: 'success' }); // Stats const [detailedStats, setDetailedStats] = useState<{ totalCalls: number; todayCount: number; dailyTrend: {date: string, count: number}[]; modelDistribution: {name: string, value: number}[]; } | null>(null); // Key Management const [geminiKeys, setGeminiKeys] = useState([]); const [openRouterKeys, setOpenRouterKeys] = useState([]); const [doubaoKeys, setDoubaoKeys] = useState([]); // New Doubao State const [newGeminiKey, setNewGeminiKey] = useState(''); const [newOpenRouterKey, setNewOpenRouterKey] = useState(''); const [newDoubaoKey, setNewDoubaoKey] = useState(''); // New Doubao Input // Model Management - OpenRouter const [orModels, setOrModels] = useState([]); const [newModelId, setNewModelId] = useState(''); const [newModelName, setNewModelName] = useState(''); const [newModelApiUrl, setNewModelApiUrl] = useState(''); // Model Management - Doubao const [doubaoModels, setDoubaoModels] = useState([]); const [newDoubaoModelId, setNewDoubaoModelId] = useState(''); // Model ID (e.g. doubao-pro) const [newDoubaoEndpointId, setNewDoubaoEndpointId] = useState(''); // Endpoint ID (e.g. ep-xxxx) const [newDoubaoModelName, setNewDoubaoModelName] = useState(''); // Provider Priority const [providerOrder, setProviderOrder] = useState(ALL_PROVIDERS); useEffect(() => { loadData(); }, []); const loadData = async () => { try { const cfg = await api.config.get(); setSystemConfig(cfg); if (cfg.apiKeys) { setGeminiKeys(cfg.apiKeys.gemini || []); setOpenRouterKeys(cfg.apiKeys.openrouter || []); setDoubaoKeys(cfg.apiKeys.doubao || []); // Load Doubao keys } setOrModels(cfg.openRouterModels && cfg.openRouterModels.length > 0 ? cfg.openRouterModels : DEFAULT_OR_MODELS); // Load Doubao Models setDoubaoModels(cfg.doubaoModels || []); // Fix: Merge saved order with any new providers (like DOUBAO) that might be missing from old configs let currentOrder = cfg.aiProviderOrder || []; if (currentOrder.length === 0) { currentOrder = [...ALL_PROVIDERS]; } else { // Check if any standard provider is missing from the saved config and append it ALL_PROVIDERS.forEach(p => { if (!currentOrder.includes(p)) { currentOrder.push(p); } }); } setProviderOrder(currentOrder); const stats = await api.ai.getStats(); setDetailedStats(stats); } catch (e) { console.error("Failed to load admin data", e); } }; const toggleSystemAI = async () => { if (!systemConfig) return; try { const newVal = !systemConfig.enableAI; await api.config.save({ ...systemConfig, enableAI: newVal }); setSystemConfig({ ...systemConfig, enableAI: newVal }); setToast({ show: true, message: `AI 服务已${newVal ? '开启' : '关闭'}`, type: 'success' }); } catch (e) { setToast({ show: true, message: '操作失败', type: 'error' }); } }; const handleAddKey = (type: 'gemini' | 'openrouter' | 'doubao') => { const keyMap = { gemini: newGeminiKey, openrouter: newOpenRouterKey, doubao: newDoubaoKey }; const key = keyMap[type].trim(); if (!key) return; if (type === 'gemini') { setGeminiKeys([...geminiKeys, key]); setNewGeminiKey(''); } else if (type === 'openrouter') { setOpenRouterKeys([...openRouterKeys, key]); setNewOpenRouterKey(''); } else if (type === 'doubao') { setDoubaoKeys([...doubaoKeys, key]); setNewDoubaoKey(''); } }; const removeKey = (type: 'gemini' | 'openrouter' | 'doubao', index: number) => { if (type === 'gemini') setGeminiKeys(geminiKeys.filter((_, i) => i !== index)); else if (type === 'openrouter') setOpenRouterKeys(openRouterKeys.filter((_, i) => i !== index)); else if (type === 'doubao') setDoubaoKeys(doubaoKeys.filter((_, i) => i !== index)); }; // --- Model Management Functions --- // OpenRouter const handleAddModel = () => { if (!newModelId.trim()) return; setOrModels([...orModels, { id: newModelId.trim(), name: newModelName.trim() || newModelId.trim(), apiUrl: newModelApiUrl.trim() || undefined, isCustom: true }]); setNewModelId(''); setNewModelName(''); setNewModelApiUrl(''); }; const handleRemoveModel = (idx: number) => setOrModels(orModels.filter((_, i) => i !== idx)); const handleMoveModel = (idx: number, direction: -1 | 1) => { const newArr = [...orModels]; const targetIdx = idx + direction; if (targetIdx < 0 || targetIdx >= newArr.length) return; [newArr[idx], newArr[targetIdx]] = [newArr[targetIdx], newArr[idx]]; setOrModels(newArr); }; // Doubao const handleAddDoubaoModel = () => { if (!newDoubaoEndpointId.trim()) return; // Endpoint ID is crucial setDoubaoModels([...doubaoModels, { modelId: newDoubaoModelId.trim() || newDoubaoEndpointId.trim(), endpointId: newDoubaoEndpointId.trim(), name: newDoubaoModelName.trim() || newDoubaoModelId.trim() || 'Doubao Model' }]); setNewDoubaoModelId(''); setNewDoubaoEndpointId(''); setNewDoubaoModelName(''); }; const handleRemoveDoubaoModel = (idx: number) => setDoubaoModels(doubaoModels.filter((_, i) => i !== idx)); const handleMoveDoubaoModel = (idx: number, direction: -1 | 1) => { const newArr = [...doubaoModels]; const targetIdx = idx + direction; if (targetIdx < 0 || targetIdx >= newArr.length) return; [newArr[idx], newArr[targetIdx]] = [newArr[targetIdx], newArr[idx]]; setDoubaoModels(newArr); }; const handleMoveProviderOrder = (idx: number, direction: -1 | 1) => { const newArr = [...providerOrder]; const targetIdx = idx + direction; if (targetIdx < 0 || targetIdx >= newArr.length) return; [newArr[idx], newArr[targetIdx]] = [newArr[targetIdx], newArr[idx]]; setProviderOrder(newArr); }; const saveApiKeys = async () => { if (!systemConfig) return; try { await api.config.save({ ...systemConfig, apiKeys: { gemini: geminiKeys, openrouter: openRouterKeys, doubao: doubaoKeys }, openRouterModels: orModels, doubaoModels: doubaoModels, // NEW aiProviderOrder: providerOrder }); await api.ai.resetPool(); setToast({ show: true, message: 'API 配置及模型列表已保存', type: 'success' }); } catch (e) { setToast({ show: true, message: '保存失败', type: 'error' }); } }; return (
{toast.show && setToast({...toast, show: false})}/>}

AI 智能助教管理后台

监控 AI 服务状态与用量,管理密钥池。

{/* STATS PANEL */}

调用数据分析

{detailedStats?.totalCalls || 0}
历史累计调用
{detailedStats?.todayCount || 0}
今日调用次数
当前状态: {systemConfig?.enableAI ? 服务运行中 : 服务已暂停}

近7日调用趋势

val.slice(5)}/>

模型使用分布

{(detailedStats?.modelDistribution || []).map((entry, index) => ())}

服务控制

AI 服务总开关

关闭后所有用户将无法使用 AI 功能

多线路密钥池配置

{/* Gemini Keys */}
{geminiKeys.length} 个

当额度耗尽时自动切换。

{geminiKeys.map((k, idx) => (
{k.substring(0, 8)}...{k.substring(k.length - 6)}
))}
setNewGeminiKey(e.target.value)}/>
{/* Doubao Keys */}
{doubaoKeys.length} 个

使用原生 Axios 调用。

{doubaoKeys.map((k, idx) => (
{k.substring(0, 8)}...{k.substring(k.length - 6)}
))}
setNewDoubaoKey(e.target.value)}/>
{/* OpenRouter Keys */}
{openRouterKeys.length} 个

备用线路,支持自定义模型。

{openRouterKeys.map((k, idx) => (
{k.substring(0, 8)}...{k.substring(k.length - 6)}
))}
setNewOpenRouterKey(e.target.value)}/>
{/* Provider Order Management */}

大模型调用优先级

系统将按照以下顺序尝试调用大模型。如果前一个服务商额度耗尽或报错,会自动切换到下一个。

{providerOrder.map((provider, idx) => (
{idx + 1}
{provider}
))}
{/* DOUBAO MODEL MANAGEMENT */}

豆包/火山引擎模型管理

配置说明:

  • 接入点 ID (Endpoint ID): 必填。API 调用核心凭证,格式如 ep-2024060401xxxx-xxxxx
  • 模型 ID (Model ID): 选填。模型本身的名称(如 doubao-pro-32k),用于记录统计。
{doubaoModels.length === 0 ?
暂无配置模型,请在下方添加。
: doubaoModels.map((m, idx) => (
{m.name || 'Doubao Model'}
EP: {m.endpointId}
ID: {m.modelId || '-'}
{idx === 0 && 默认}
))}
setNewDoubaoEndpointId(e.target.value)} placeholder="如: ep-2024xxxx-xxxxx"/>
setNewDoubaoModelId(e.target.value)} placeholder="如: doubao-pro-32k"/>
setNewDoubaoModelName(e.target.value)} placeholder="如: 豆包 Pro"/>
{/* OPENROUTER MODEL MANAGEMENT */}

OpenAI 格式大模型列表管理

{orModels.map((m, idx) => (
{m.name || m.id}
ID: {m.id}
{m.apiUrl &&
API: {m.apiUrl}
}
{m.isCustom ? (自定义) : (内置)}
))}
setNewModelId(e.target.value)} placeholder="如: gpt-4o"/>
setNewModelName(e.target.value)} placeholder="如: GPT-4o"/>
setNewModelApiUrl(e.target.value)} placeholder="https://api.openai.com/v1"/>
); };