205 lines
6.5 KiB
TypeScript
205 lines
6.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
|
|
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000'
|
|
|
|
interface Provider {
|
|
id: number
|
|
name: string
|
|
model_id: string
|
|
base_url: string | null
|
|
is_active: boolean
|
|
is_default: boolean
|
|
}
|
|
|
|
export default function ProvidersPage() {
|
|
const [providers, setProviders] = useState<Provider[]>([])
|
|
const [showForm, setShowForm] = useState(false)
|
|
const [editId, setEditId] = useState<number | null>(null)
|
|
const [form, setForm] = useState({
|
|
name: '',
|
|
model_id: '',
|
|
base_url: '',
|
|
api_key: '',
|
|
is_default: false,
|
|
})
|
|
|
|
const getToken = () => localStorage.getItem('admin_token') || ''
|
|
|
|
const fetchProviders = async () => {
|
|
const res = await fetch(`${API_BASE}/api/v1/admin/providers`, {
|
|
headers: { Authorization: `Bearer ${getToken()}` },
|
|
})
|
|
if (res.ok) {
|
|
setProviders(await res.json())
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchProviders()
|
|
}, [])
|
|
|
|
const handleSubmit = async () => {
|
|
const url = editId
|
|
? `${API_BASE}/api/v1/admin/providers/${editId}`
|
|
: `${API_BASE}/api/v1/admin/providers`
|
|
const method = editId ? 'PUT' : 'POST'
|
|
|
|
await fetch(url, {
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${getToken()}`,
|
|
},
|
|
body: JSON.stringify(form),
|
|
})
|
|
|
|
setShowForm(false)
|
|
setEditId(null)
|
|
setForm({ name: '', model_id: '', base_url: '', api_key: '', is_default: false })
|
|
fetchProviders()
|
|
}
|
|
|
|
const handleDelete = async (id: number) => {
|
|
if (!confirm('确定删除?')) return
|
|
await fetch(`${API_BASE}/api/v1/admin/providers/${id}`, {
|
|
method: 'DELETE',
|
|
headers: { Authorization: `Bearer ${getToken()}` },
|
|
})
|
|
fetchProviders()
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h1 className="text-2xl font-bold">AI 配置管理</h1>
|
|
<button
|
|
onClick={() => setShowForm(true)}
|
|
className="px-4 py-2 bg-blue-600 text-white rounded"
|
|
>
|
|
添加配置
|
|
</button>
|
|
</div>
|
|
|
|
{/* Provider 列表 */}
|
|
<div className="bg-white rounded shadow overflow-hidden">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left">名称</th>
|
|
<th className="px-4 py-3 text-left">模型 ID</th>
|
|
<th className="px-4 py-3 text-left">Base URL</th>
|
|
<th className="px-4 py-3 text-left">状态</th>
|
|
<th className="px-4 py-3 text-left">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{providers.map((p) => (
|
|
<tr key={p.id} className="border-t">
|
|
<td className="px-4 py-3">
|
|
{p.name}
|
|
{p.is_default && (
|
|
<span className="ml-2 text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
|
|
默认
|
|
</span>
|
|
)}
|
|
</td>
|
|
<td className="px-4 py-3">{p.model_id}</td>
|
|
<td className="px-4 py-3">{p.base_url || '-'}</td>
|
|
<td className="px-4 py-3">
|
|
<span className={p.is_active ? 'text-green-600' : 'text-gray-400'}>
|
|
{p.is_active ? '启用' : '禁用'}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3 space-x-2">
|
|
<button
|
|
onClick={() => handleDelete(p.id)}
|
|
className="text-red-600 hover:underline"
|
|
>
|
|
删除
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* 添加/编辑表单 */}
|
|
{showForm && (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
|
|
<div className="bg-white p-6 rounded shadow-lg w-96">
|
|
<h2 className="text-xl font-bold mb-4">
|
|
{editId ? '编辑' : '添加'} AI 配置
|
|
</h2>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm mb-1">名称</label>
|
|
<input
|
|
value={form.name}
|
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
|
className="w-full p-2 border rounded"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">模型 ID</label>
|
|
<input
|
|
value={form.model_id}
|
|
onChange={(e) => setForm({ ...form, model_id: e.target.value })}
|
|
className="w-full p-2 border rounded"
|
|
placeholder="gpt-4o-mini"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">Base URL</label>
|
|
<input
|
|
value={form.base_url}
|
|
onChange={(e) => setForm({ ...form, base_url: e.target.value })}
|
|
className="w-full p-2 border rounded"
|
|
placeholder="https://api.openai.com/v1"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">API Key</label>
|
|
<input
|
|
type="password"
|
|
value={form.api_key}
|
|
onChange={(e) => setForm({ ...form, api_key: e.target.value })}
|
|
className="w-full p-2 border rounded"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={form.is_default}
|
|
onChange={(e) => setForm({ ...form, is_default: e.target.checked })}
|
|
className="mr-2"
|
|
/>
|
|
设为默认
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-2 mt-6">
|
|
<button
|
|
onClick={() => setShowForm(false)}
|
|
className="px-4 py-2 border rounded"
|
|
>
|
|
取消
|
|
</button>
|
|
<button
|
|
onClick={handleSubmit}
|
|
className="px-4 py-2 bg-blue-600 text-white rounded"
|
|
>
|
|
保存
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
} |