'use client' import { useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import { Pencil, Plus, RefreshCcw, Star, Trash2, Wand2, ZapOff } from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Textarea } from '@/components/ui/textarea' import { AdminUnauthorizedError, adminFetchJson } from '@/lib/admin-api' interface Provider { id: number name: string model_id: string base_url: string | null is_active: boolean is_default: boolean created_at?: string } export default function ProvidersPage() { const router = useRouter() const [providers, setProviders] = useState([]) const [query, setQuery] = useState('') const [onlyActive, setOnlyActive] = useState(false) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState('') const [dialogOpen, setDialogOpen] = useState(false) const [editing, setEditing] = useState(null) const [isSaving, setIsSaving] = useState(false) const [testResult, setTestResult] = useState<{ ok: boolean latency_ms?: number message?: string } | null>(null) const [testingId, setTestingId] = useState(null) const [form, setForm] = useState({ name: '', model_id: '', base_url: '', api_key: '', is_active: true, is_default: false, }) const fetchProviders = async () => { setIsLoading(true) setError('') try { const data = await adminFetchJson('/api/v1/admin/providers') setProviders(data) } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setError(err?.message || '加载失败') } finally { setIsLoading(false) } } useEffect(() => { fetchProviders() }, []) const filteredProviders = useMemo(() => { const q = query.trim().toLowerCase() return providers.filter((p) => { if (onlyActive && !p.is_active) return false if (!q) return true return ( p.name.toLowerCase().includes(q) || p.model_id.toLowerCase().includes(q) || (p.base_url || '').toLowerCase().includes(q) ) }) }, [providers, query, onlyActive]) const openCreate = () => { setEditing(null) setTestResult(null) setForm({ name: '', model_id: '', base_url: '', api_key: '', is_active: true, is_default: false, }) setDialogOpen(true) } const openEdit = (provider: Provider) => { setEditing(provider) setTestResult(null) setForm({ name: provider.name, model_id: provider.model_id, base_url: provider.base_url || '', api_key: '', is_active: provider.is_active, is_default: provider.is_default, }) setDialogOpen(true) } const handleSubmit = async () => { setIsSaving(true) setError('') try { if (editing) { const payload: Record = { name: form.name, model_id: form.model_id, base_url: form.base_url.trim() ? form.base_url.trim() : null, is_active: form.is_active, is_default: form.is_default, } if (form.api_key.trim()) payload.api_key = form.api_key.trim() await adminFetchJson(`/api/v1/admin/providers/${editing.id}`, { method: 'PUT', body: JSON.stringify(payload), }) } else { await adminFetchJson(`/api/v1/admin/providers`, { method: 'POST', body: JSON.stringify({ name: form.name, model_id: form.model_id, base_url: form.base_url.trim() ? form.base_url.trim() : null, api_key: form.api_key.trim(), is_active: form.is_active, is_default: form.is_default, }), }) } setDialogOpen(false) setEditing(null) setForm({ name: '', model_id: '', base_url: '', api_key: '', is_active: true, is_default: false, }) await fetchProviders() } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setError(err?.message || '保存失败') } finally { setIsSaving(false) } } const handleDelete = async (provider: Provider) => { if (!confirm('确定删除?')) return setError('') try { await adminFetchJson(`/api/v1/admin/providers/${provider.id}`, { method: 'DELETE' }) await fetchProviders() } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setError(err?.message || '删除失败') } } const setDefault = async (provider: Provider) => { setError('') try { await adminFetchJson(`/api/v1/admin/providers/${provider.id}`, { method: 'PUT', body: JSON.stringify({ is_default: true }), }) await fetchProviders() } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setError(err?.message || '操作失败') } } const toggleActive = async (provider: Provider) => { setError('') try { await adminFetchJson(`/api/v1/admin/providers/${provider.id}`, { method: 'PUT', body: JSON.stringify({ is_active: !provider.is_active }), }) await fetchProviders() } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setError(err?.message || '操作失败') } } const testProvider = async (provider: Provider) => { setTestResult(null) setTestingId(provider.id) setError('') try { const data = await adminFetchJson<{ ok: boolean latency_ms?: number message?: string }>(`/api/v1/admin/providers/${provider.id}/test`, { method: 'POST' }) setTestResult(data) } catch (err: any) { if (err instanceof AdminUnauthorizedError) { router.replace('/login') return } setTestResult({ ok: false, message: err?.message || '测试失败' }) } finally { setTestingId(null) } } return (
AI 配置管理 管理 AI Provider(模型、Base URL、API Key、默认/启用状态)
{error && (
{error}
)} {testResult && (
{testResult.ok ? (
测试成功{typeof testResult.latency_ms === 'number' ? `(延迟 ${testResult.latency_ms}ms)` : ''}
) : (
{testResult.message || '测试失败'}
)}
)}
setQuery(e.target.value)} placeholder="搜索:名称 / 模型 / Base URL" />
名称 模型 Base URL 状态 操作 {isLoading ? ( 加载中... ) : filteredProviders.length === 0 ? ( 暂无数据 ) : ( filteredProviders.map((p) => (
{p.name}
{p.is_default && 默认}
{p.created_at && (
创建于 {new Date(p.created_at).toLocaleString()}
)}
{p.model_id}
{p.base_url || '-'}
{p.is_active ? ( 启用 ) : ( 禁用 )}
{!p.is_default && ( )}
)) )}
{dialogOpen && (
{editing ? '编辑' : '添加'} AI 配置 {editing ? '更新配置项(API Key 留空则不修改)' : '添加新的 Provider 配置'} {testResult && (
{testResult.ok ? (
连接正常{typeof testResult.latency_ms === 'number' ? `(${testResult.latency_ms}ms)` : ''}
) : (
{testResult.message || '测试失败'}
)}
)}
setForm({ ...form, name: e.target.value })} placeholder="OpenAI / Azure / 自建代理..." />
setForm({ ...form, model_id: e.target.value })} placeholder="gpt-4o-mini" />
setForm({ ...form, base_url: e.target.value })} placeholder="https://api.openai.com/v1" />
留空表示使用默认 OpenAI Base URL