import { FormEvent, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { Eye, KeyRound, Pencil, Save, Trash2, X } from 'lucide-react' import { apiConfigsAPI, type APIConfig } from '../api/api_configs' import { confirmAction } from './confirmAction' // Compact inline-editable provider table for use inside AdminPage's "外部API" tab. export function APIConfigsPanel() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [editing, setEditing] = useState(null) const refresh = () => apiConfigsAPI .list() .then(setItems) .finally(() => setLoading(false)) useEffect(() => { refresh().catch(() => undefined) }, []) return (

外部 API 配置

TMDb / Bangumi / TheTVDB / Fanart / OpenAI / Douban / Adult 密钥与源管理 · AES-GCM 加密存储

{loading && (

加载中…

)} {!loading && (
{items.map((item) => editing === item.provider ? ( setEditing(null)} onSaved={() => { setEditing(null) refresh() }} /> ) : ( ), )}
服务 密钥 状态 操作

{item.provider}

{item.description && (

{item.description}

)}
{apiConfigConfigured(item) ? ( {item.masked_key} ) : ( 未配置 )} {item.provider === 'adult' && apiConfigSourceCount(item) > 0 ? ( {apiConfigSourceCount(item)} 个源 ) : null} {apiConfigConfigured(item) ? ( 已配置 ) : ( 未配置 )}
{item.has_key && ( )}
)}
) } function EditingRow({ item, onCancel, onSaved, }: { item: APIConfig onCancel: () => void onSaved: () => void }) { const [apiKey, setAPIKey] = useState('') const [baseURL, setBaseURL] = useState(item.base_url ?? '') const [extra, setExtra] = useState(item.extra ?? '') const [enabled, setEnabled] = useState(item.enabled) const [saving, setSaving] = useState(false) const isAdult = item.provider === 'adult' const submit = async (e: FormEvent) => { e.preventDefault() setSaving(true) try { const patch: Record = { base_url: baseURL, enabled } if (isAdult) patch.extra = extra if (apiKey.trim()) patch.api_key = apiKey.trim() await apiConfigsAPI.update(item.provider, patch) toast.success(`${item.provider} 已保存`) onSaved() } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '保存失败' toast.error(msg) } finally { setSaving(false) } } return (
{item.provider} {isAdult && (