'use client' import { useState } from 'react' import useSWR from 'swr' import { Plus, Loader2, ChevronLeft, ChevronRight, Pencil, Trash2, } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from '@/components/ui/dialog' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { TableSkeleton } from '@/components/ui/skeleton' import { ErrorBanner, EmptyState } from '@/components/ui/state' import { api } from '@/lib/api-client' import { ApiRequestError } from '@/lib/api-client' import { formatNumber } from '@/lib/utils' import type { Model, Provider } from '@/lib/types' const PAGE_SIZE = 20 interface ModelForm { provider_id: string model_id: string alias: string context_window: string max_output_tokens: string supports_streaming: boolean supports_vision: boolean enabled: boolean pricing_input: string pricing_output: string } const emptyForm: ModelForm = { provider_id: '', model_id: '', alias: '', context_window: '4096', max_output_tokens: '4096', supports_streaming: true, supports_vision: false, enabled: true, pricing_input: '', pricing_output: '', } export default function ModelsPage() { const [page, setPage] = useState(1) const [providerFilter, setProviderFilter] = useState('all') const [error, setError] = useState('') // SWR for models list const { data, isLoading, mutate } = useSWR( ['models', page, providerFilter], () => { const params: Record = { page, page_size: PAGE_SIZE } if (providerFilter !== 'all') params.provider_id = providerFilter return api.models.list(params) } ) const models = data?.items ?? [] const total = data?.total ?? 0 // SWR for providers list (dropdown) const { data: providersData } = useSWR( ['providers.all'], () => api.providers.list({ page: 1, page_size: 100 }) ) const providers = providersData?.items ?? [] // Dialog const [dialogOpen, setDialogOpen] = useState(false) const [editTarget, setEditTarget] = useState(null) const [form, setForm] = useState(emptyForm) const [saving, setSaving] = useState(false) // 删除 const [deleteTarget, setDeleteTarget] = useState(null) const [deleting, setDeleting] = useState(false) const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)) const providerMap = new Map(providers.map((p) => [p.id, p.display_name || p.name])) function openCreateDialog() { setEditTarget(null) setForm(emptyForm) setDialogOpen(true) } function openEditDialog(model: Model) { setEditTarget(model) setForm({ provider_id: model.provider_id, model_id: model.model_id, alias: model.alias, context_window: model.context_window.toString(), max_output_tokens: model.max_output_tokens.toString(), supports_streaming: model.supports_streaming, supports_vision: model.supports_vision, enabled: model.enabled, pricing_input: model.pricing_input.toString(), pricing_output: model.pricing_output.toString(), }) setDialogOpen(true) } async function handleSave() { if (!form.model_id.trim() || !form.provider_id) return setSaving(true) try { const payload = { provider_id: form.provider_id, model_id: form.model_id.trim(), alias: form.alias.trim(), context_window: parseInt(form.context_window, 10) || 4096, max_output_tokens: parseInt(form.max_output_tokens, 10) || 4096, supports_streaming: form.supports_streaming, supports_vision: form.supports_vision, enabled: form.enabled, pricing_input: parseFloat(form.pricing_input) || 0, pricing_output: parseFloat(form.pricing_output) || 0, } if (editTarget) { await api.models.update(editTarget.id, payload) } else { await api.models.create(payload) } setDialogOpen(false) mutate() } catch (err) { if (err instanceof ApiRequestError) setError(err.body.message) } finally { setSaving(false) } } async function handleDelete() { if (!deleteTarget) return setDeleting(true) try { await api.models.delete(deleteTarget.id) setDeleteTarget(null) mutate() } catch (err) { if (err instanceof ApiRequestError) setError(err.body.message) } finally { setDeleting(false) } } return (
{error && setError('')} />} {isLoading ? ( ) : error ? null : models.length === 0 ? ( ) : ( <> 模型 ID 别名 服务商 上下文窗口 最大输出 流式 视觉 启用 操作 {models.map((m) => ( {m.model_id} {m.alias || '-'} {providerMap.get(m.provider_id) || m.provider_id.slice(0, 8)} {formatNumber(m.context_window)} {formatNumber(m.max_output_tokens)} {m.supports_streaming ? '是' : '否'} {m.supports_vision ? '是' : '否'} {m.enabled ? '启用' : '禁用'}
))}

第 {page} 页 / 共 {totalPages} 页 ({total} 条)

)} {/* 创建/编辑 Dialog */} {editTarget ? '编辑模型' : '新建模型'} {editTarget ? '修改模型配置' : '添加新的 AI 模型'}
setForm({ ...form, model_id: e.target.value })} placeholder="gpt-4o" disabled={!!editTarget} />
setForm({ ...form, alias: e.target.value })} placeholder="GPT-4o" />
setForm({ ...form, context_window: e.target.value })} />
setForm({ ...form, max_output_tokens: e.target.value })} />
setForm({ ...form, pricing_input: e.target.value })} placeholder="0" />
setForm({ ...form, pricing_output: e.target.value })} placeholder="0" />
setForm({ ...form, supports_streaming: v })} />
setForm({ ...form, supports_vision: v })} />
setForm({ ...form, enabled: v })} />
{/* 删除确认 */} setDeleteTarget(null)}> 确认删除 确定要删除模型 "{deleteTarget?.alias || deleteTarget?.model_id}" 吗?此操作不可撤销。
) }