import React, { useState, useEffect, useMemo } from 'react'; import { Key, Plus, Trash2, Copy, Check, Shield, Loader2, RefreshCw, KeyRound, MoreVertical, Edit2, BookOpen, Zap, CreditCard, Terminal, Layers, Search, Filter, ArrowRight, BarChart3, Cpu, Activity, Clock, X } from 'lucide-react'; import { useStore } from '@/store/useStore'; import { motion, AnimatePresence } from 'framer-motion'; import { useTranslation } from 'react-i18next'; import PageContainer from '@/components/PageContainer'; interface ApiKey { id: string; key_name: string; key_secret: string; last_used: string | null; created_at: string; status: 'active' | 'inactive'; scopes: string[]; } type Tab = 'overview' | 'keys' | 'docs'; type Lang = 'curl' | 'python' | 'node'; export default function DeveloperHub() { const { t } = useTranslation(); const { token } = useStore(); const [activeTab, setActiveTab] = useState('overview'); const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(true); const [isCreating, setIsCreating] = useState(false); const [newKeyName, setNewKeyName] = useState(''); const [createdKey, setCreatedKey] = useState<{ key_name: string, key_secret: string } | null>(null); const [copied, setCopied] = useState(null); const [showMenuId, setShowMenuId] = useState(null); const [editingId, setEditingId] = useState(null); const [editName, setEditName] = useState(''); // Search & Filter const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>('all'); // Docs 相关 const [selectedKeyForDocs, setSelectedKeyForDocs] = useState('YOUR_API_KEY'); const [selectedLang, setSelectedLang] = useState('curl'); const [isRevealing, setIsRevealing] = useState(null); const fetchKeys = async () => { try { const response = await fetch('/api/apikey/list', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); if (data.success) { setKeys(data.keys); } } catch { // Ignore } setLoading(false); }; useEffect(() => { fetchKeys(); }, [token]); // 点击外部关闭菜单 useEffect(() => { const handleClickOutside = () => setShowMenuId(null); document.addEventListener('click', handleClickOutside); return () => document.removeEventListener('click', handleClickOutside); }, []); const filteredKeys = useMemo(() => { return keys.filter(k => { const matchesSearch = k.key_name.toLowerCase().includes(searchQuery.toLowerCase()); const matchesStatus = statusFilter === 'all' || k.status === statusFilter; return matchesSearch && matchesStatus; }); }, [keys, searchQuery, statusFilter]); const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); if (!newKeyName.trim()) return; try { const response = await fetch('/api/apikey/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ name: newKeyName.trim(), scopes: ['all'] }), }); const data = await response.json(); if (data.success) { setCreatedKey(data.key); setIsCreating(false); setNewKeyName(''); fetchKeys(); } } catch { // Ignore } }; const handleDelete = async (id: string) => { if (!confirm(t('developer.keys.confirm_delete'))) return; try { await fetch(`/api/apikey/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); fetchKeys(); } catch { // Ignore } }; const handleToggleStatus = async (key: ApiKey) => { const newStatus = key.status === 'active' ? 'inactive' : 'active'; try { await fetch(`/api/apikey/${key.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ status: newStatus }), }); fetchKeys(); } catch { // Ignore } }; const handleUpdateName = async (id: string) => { if (!editName.trim()) return; try { await fetch(`/api/apikey/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ name: editName.trim() }), }); setEditingId(null); fetchKeys(); } catch { // Ignore } }; const handleRegenerate = async (id: string) => { if (!confirm(t('developer.keys.confirm_regenerate'))) return; try { const response = await fetch(`/api/apikey/${id}/regenerate`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); if (data.success) { const keyName = keys.find(k => k.id === id)?.key_name || t('developer.keys.default_key_name'); setCreatedKey({ key_name: keyName, key_secret: data.key_secret }); fetchKeys(); } } catch { // Ignore } }; const handleRevealAndCopy = async (id: string) => { setIsRevealing(id); try { const response = await fetch(`/api/apikey/${id}/reveal`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); if (data.success) { copyToClipboard(data.key_secret, id); } } catch { // Ignore } setIsRevealing(null); }; const copyToClipboard = (text: string, id: string = 'global') => { navigator.clipboard.writeText(text); setCopied(id); setTimeout(() => setCopied(null), 2000); }; const baseUrl = window.location.origin + '/api/v1'; // 获取显示的 Secret const getSecretForDocs = () => { if (selectedKeyForDocs === 'YOUR_API_KEY') return 'sk_********************'; const k = keys.find(k => k.id === selectedKeyForDocs); return k ? k.key_secret : 'sk_********************'; }; // 生成代码片段 const getCodeSnippet = (lang: Lang) => { const secret = getSecretForDocs(); const endpoint = `${baseUrl}/chat/completions`; switch (lang) { case 'curl': return `curl ${endpoint} \\ -H "Content-Type: application/json" \\ -H "Authorization: Bearer ${secret}" \\ -d '{ "model": "codex-v1", "messages": [{"role": "user", "content": "你好"}] }'`; case 'python': return `import openai client = openai.OpenAI( api_key="${secret}", base_url="${baseUrl}" ) response = client.chat.completions.create( model="codex-v1", messages=[{"role": "user", "content": "你好"}] ) print(response.choices[0].message.content)`; case 'node': return `import OpenAI from 'openai'; const client = new OpenAI({ apiKey: '${secret}', baseURL: '${baseUrl}' }); async function main() { const response = await client.chat.completions.create({ model: 'codex-v1', messages: [{ role: 'user', content: '你好' }], }); console.log(response.choices[0].message.content); } main();`; default: return ''; } }; return ( {/* Header & Tabs */}

{t('developer.title')}

{t('developer.subtitle')}

{(['overview', 'keys', 'docs'] as Tab[]).map((tab) => ( ))}
{activeTab === 'overview' && ( {/* 快速开始卡片 */}

{t('developer.quick_start.get_key')}

{t('developer.quick_start.get_key_desc')}

{t('developer.docs.title')}

{t('developer.quick_start.interactive_docs_desc')}

{t('developer.quick_start.usage_overview')}

{t('developer.quick_start.usage_overview_desc')}

{/* 模拟统计图表部分 */}

{t('developer.quick_start.call_trend')}

{t('developer.quick_start.call_trend_desc')}

{t('developer.quick_start.requests')}
{[45, 78, 56, 92, 120, 88, 65].map((val, i) => (
{val * 10} req
03-{13-6+i}
))}
)} {activeTab === 'keys' && ( {/* Keys Tab Header */}
setSearchQuery(e.target.value)} className="w-full pl-12 pr-4 py-3 bg-white border border-zinc-200 rounded-2xl focus:ring-2 focus:ring-blue-500 outline-none transition-all shadow-sm" />
{loading ? (
) : (
{filteredKeys.map((key) => ( ))} {filteredKeys.length === 0 && ( )}
{t('developer.keys.table.name')} {t('developer.keys.table.key')} {t('developer.keys.table.status')} {t('developer.keys.table.last_used')} {t('developer.keys.table.actions')}
{editingId === key.id ? (
setEditName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleUpdateName(key.id); if (e.key === 'Escape') setEditingId(null); }} />
) : (
{key.key_name}
)}
{t('developer.keys.table.created_at', { date: new Date(key.created_at).toLocaleDateString() })}
{key.key_secret}
{key.last_used ? new Date(key.last_used).toLocaleString() : t('developer.keys.table.never_used')}
{showMenuId === key.id && (
)}

{t('developer.keys.no_match')}

{t('developer.keys.no_match_desc')}

)}
)} {activeTab === 'docs' && ( {/* Docs Tab Content */}

{t('developer.docs.title')}

{t('developer.docs.subtitle')}

{selectedKeyForDocs !== 'YOUR_API_KEY' && ( )}
{/* API 1: Chat */}

{t('developer.docs.chat_title')}

{t('developer.docs.chat_desc')}

POST /chat/completions
{t('developer.docs.auth_header')}
Authorization: Bearer {selectedKeyForDocs === 'YOUR_API_KEY' ? 'sk_********************' : keys.find(k => k.id === selectedKeyForDocs)?.key_secret}

{t('developer.docs.https_tip')}

{t('developer.docs.params')}
{[ { name: 'messages', type: 'Array', desc: t('developer.docs.param_messages'), req: true }, { name: 'model', type: 'String', desc: t('developer.docs.param_model'), req: false }, { name: 'stream', type: 'Boolean', desc: t('developer.docs.param_stream'), req: false }, ].map((param) => (
{param.name} ({param.type})
{param.desc} {param.req ? t('developer.docs.required') : t('developer.docs.optional')}
))}
{(['curl', 'python', 'node'] as Lang[]).map((lang) => ( ))}
                          {getCodeSnippet(selectedLang)}
                        
{/* API 2: Quota */}

{t('developer.docs.quota_title')}

{t('developer.docs.quota_desc')}

GET /user/quota
{t('developer.docs.req_example')}
{`curl ${baseUrl}/user/quota \\
-H "Authorization: Bearer ${selectedKeyForDocs === 'YOUR_API_KEY' ? 'YOUR_API_KEY' : 'FULL_SECRET'}"`}
                    
{t('developer.docs.res_example')}
{`{
  "email": "user@example.com",
  "plan": "pro",
  "quota_remaining": 492
}`}
                    
)}
{/* 创建模态框 (与之前一致,保持简洁) */} {isCreating && (

{t('developer.keys.modal.new_key_title')}

setNewKeyName(e.target.value)} placeholder={t('developer.keys.modal.key_name_placeholder')} className="w-full px-5 py-4 bg-zinc-50 border border-zinc-200 rounded-2xl focus:ring-2 focus:ring-blue-500 outline-none transition-all" />
)} {/* 成功展示模态框 (仅显示一次 Secret) */} {createdKey && (

{t('developer.keys.modal.success_title')}

{t('developer.keys.modal.success_desc')}

{createdKey.key_secret}
)}
); } const CloseIcon = ({ size, className }: any) => { return ( ); };