feat:初版

This commit is contained in:
2025-12-25 18:41:09 +08:00
commit 1429e0e66a
52 changed files with 2688 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
'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>
)
}