"use client"; import { PlusOutlined } from "@ant-design/icons"; import { useGetIdentity } from "@refinedev/core"; import { List } from "@refinedev/antd"; import { App, Button, Card, Col, Form, Input, InputNumber, Modal, Row, Select, Space, Switch, Table, Tag, Typography, type TableColumnsType, } from "antd"; import { useCallback, useEffect, useState } from "react"; import { adminRequestJson, type AdminIdentity } from "@/lib/admin/providers"; import { createDiscoveredModelId } from "./model-id"; import { formatAdminDate } from "./resource-table"; const { Text } = Typography; type ProviderType = "openai" | "openai-compatible" | "anthropic"; type Provider = { id: string; code: string; name: string; providerType: ProviderType; baseUrl: string | null; secretConfigured: boolean; enabled: boolean; updatedAt: string; }; type ModelVersion = { id: string; configId: string; modelId: string; version: number; providerId: string; providerCode: string; label: string; description: string; providerModel: string; modelTier: "standard" | "premium" | "internal"; creditCost: number; contextWindow: number | null; inputCostMicrousdPerMillion: number; outputCostMicrousdPerMillion: number; enabled: boolean; isDefault: boolean; fallbackModelId: string | null; status: string; settings: Record; createdAt: string; publishedAt: string | null; }; type ProviderForm = { name: string; providerType: ProviderType; baseUrl: string | null; apiKey?: string; enabled: boolean; }; type ModelForm = Pick; type ModelsPayload = { data: ModelVersion[]; total: number; providers: Provider[] }; type DiscoveredModel = { id: string; label?: string }; type DiscoveredModelsPayload = { data: DiscoveredModel[] }; type ModelFilters = { q?: string; status?: string }; type VersionAction = { action: "publish" | "rollback"; model: ModelVersion }; const providerTypeLabels: Record = { openai: "OpenAI 官方", anthropic: "Anthropic 官方", "openai-compatible": "OpenAI 兼容接口", }; const modelTierLabels: Record = { standard: "标准", premium: "高级", internal: "内部", }; const modelStatusLabels: Record = { draft: "草稿", published: "已发布", retired: "已下线", }; export default function ModelManagement() { const { message } = App.useApp(); const { data: identity } = useGetIdentity(); const [providerForm] = Form.useForm(); const [modelForm] = Form.useForm(); const [filterForm] = Form.useForm(); const selectedProviderId = Form.useWatch("providerId", modelForm); const [models, setModels] = useState([]); const [providers, setProviders] = useState([]); const [loading, setLoading] = useState(true); const [providerOpen, setProviderOpen] = useState(false); const [modelOpen, setModelOpen] = useState(false); const [editingProvider, setEditingProvider] = useState(null); const [editingModel, setEditingModel] = useState(null); const [saving, setSaving] = useState(false); const [discovering, setDiscovering] = useState(false); const [discoveredModels, setDiscoveredModels] = useState([]); const [actingId, setActingId] = useState(null); const [versionAction, setVersionAction] = useState(null); const [filters, setFilters] = useState({}); const canWrite = Boolean(identity?.permissions.includes("models.write")); const canTest = Boolean(identity?.permissions.includes("models.test")); const canPublish = Boolean(identity?.permissions.includes("models.publish")); const canRollback = Boolean(identity?.permissions.includes("models.rollback")); const load = useCallback(async () => { setLoading(true); try { const search = new URLSearchParams({ page: "1", pageSize: "100" }); if (filters.q) search.set("q", filters.q); if (filters.status) search.set("status", filters.status); const payload = await adminRequestJson(`/api/admin/models?${search}`); setModels(payload.data); setProviders(payload.providers); } catch (error) { message.error(error instanceof Error ? error.message : "读取模型配置失败"); } finally { setLoading(false); } }, [filters.q, filters.status, message]); useEffect(() => { const timer = window.setTimeout(() => void load(), 0); return () => window.clearTimeout(timer); }, [load]); function openProvider(provider?: Provider) { setEditingProvider(provider ?? null); providerForm.resetFields(); providerForm.setFieldsValue(provider ? { name: provider.name, providerType: provider.providerType, baseUrl: provider.baseUrl, apiKey: "", enabled: provider.enabled, } : { name: "", providerType: "openai-compatible", baseUrl: "https://", apiKey: "", enabled: false, }); setProviderOpen(true); } function openModel(model?: ModelVersion, providerId?: string) { setEditingModel(model ?? null); setDiscoveredModels(model ? [{ id: model.providerModel, label: model.label }] : []); modelForm.setFieldsValue(model ? { providerId: model.providerId, providerModel: model.providerModel, creditCost: model.creditCost, enabled: model.enabled, isDefault: model.isDefault, } : { providerId: providerId ?? providers[0]?.id, providerModel: "", creditCost: 1, enabled: false, isDefault: false, }); setModelOpen(true); } async function saveProvider(values: ProviderForm) { const apiKey = values.apiKey?.trim(); setSaving(true); try { await adminRequestJson("/api/admin/models", { method: "POST", body: JSON.stringify({ action: "saveProvider", id: editingProvider?.id ?? null, name: values.name.trim(), providerType: values.providerType, baseUrl: values.providerType === "openai-compatible" ? values.baseUrl?.trim() : null, ...(apiKey ? { apiKey } : {}), enabled: values.enabled, }), }); message.success("供应商配置已保存"); setProviderOpen(false); providerForm.resetFields(); await load(); } catch (error) { message.error(error instanceof Error ? error.message : "保存失败"); } finally { setSaving(false); } } async function discoverModels(providerId?: string) { if (!providerId) return; setDiscovering(true); try { const payload = await adminRequestJson("/api/admin/models/discover", { method: "POST", body: JSON.stringify({ providerId }), }); setDiscoveredModels(payload.data); if (!payload.data.length) message.info("未发现可用模型,请检查供应商配置后重试"); } catch (error) { message.error(error instanceof Error ? error.message : "获取模型列表失败"); } finally { setDiscovering(false); } } async function saveModel(values: ModelForm) { setSaving(true); try { const discovered = discoveredModels.find((item) => item.id === values.providerModel); const modelId = editingModel?.modelId ?? await createDiscoveredModelId(values.providerId, values.providerModel); await adminRequestJson("/api/admin/models", { method: "POST", body: JSON.stringify({ action: "saveDraft", modelId, versionId: editingModel?.id ?? null, providerId: values.providerId, label: editingModel?.label ?? (discovered?.label?.trim() || values.providerModel).slice(0, 60), providerModel: values.providerModel.trim(), creditCost: values.creditCost, enabled: values.enabled, description: editingModel?.description ?? "", modelTier: editingModel?.modelTier ?? "standard", contextWindow: editingModel?.contextWindow ?? null, inputCostMicrousdPerMillion: editingModel?.inputCostMicrousdPerMillion ?? 0, outputCostMicrousdPerMillion: editingModel?.outputCostMicrousdPerMillion ?? 0, isDefault: values.isDefault, fallbackModelId: editingModel?.fallbackModelId ?? null, settings: editingModel?.settings ?? {}, }), }); message.success("模型草稿已保存"); setModelOpen(false); await load(); } catch (error) { message.error(error instanceof Error ? error.message : "保存失败"); } finally { setSaving(false); } } async function act(body: Record, success: string, id: string) { setActingId(id); try { await adminRequestJson("/api/admin/models", { method: "POST", body: JSON.stringify(body) }); message.success(success); await load(); } finally { setActingId(null); } } async function submitVersionAction() { if (!versionAction) return; const { action, model } = versionAction; try { await act(action === "publish" ? { action, versionId: model.id } : { action, configId: model.configId, targetVersion: model.version }, action === "publish" ? "模型已发布" : "模型已回滚", model.id); setVersionAction(null); } catch (error) { message.error(error instanceof Error ? error.message : action === "publish" ? "发布失败" : "回滚失败"); } } async function testVersion(item: ModelVersion) { try { await act({ action: "test", versionId: item.id }, "连接测试通过,可在短有效期内发布", item.id); } catch (error) { message.error(error instanceof Error ? error.message : "连接测试失败"); } } const providerColumns: TableColumnsType = [ { title: "供应商", render: (_, item) => {item.name}{item.code} }, { title: "类型", dataIndex: "providerType", render: (value: ProviderType) => providerTypeLabels[value] }, { title: "地址", dataIndex: "baseUrl", render: (value, item) => value ?? (item.providerType === "anthropic" ? "Anthropic 官方" : "OpenAI 官方") }, { title: "密钥状态", render: (_, item) => {item.secretConfigured ? "已配置" : "未配置"} }, { title: "状态", dataIndex: "enabled", render: (value) => value ? 启用 : 停用 }, { title: "更新时间", dataIndex: "updatedAt", render: formatAdminDate }, { title: "操作", render: (_, item) => canWrite ? : null }, ]; const modelColumns: TableColumnsType = [ { title: "模型", render: (_, item) => {item.label}{item.modelId} · v{item.version} }, { title: "供应商模型", render: (_, item) => `${item.providerCode} / ${item.providerModel}` }, { title: "档位", dataIndex: "modelTier", render: (value: ModelVersion["modelTier"]) => {modelTierLabels[value]} }, { title: "点数", dataIndex: "creditCost" }, { title: "成本/百万 Token", render: (_, item) => `$${(item.inputCostMicrousdPerMillion / 1_000_000).toFixed(4)} / $${(item.outputCostMicrousdPerMillion / 1_000_000).toFixed(4)}` }, { title: "路由", render: (_, item) => {item.isDefault && 默认}回退模型:{item.fallbackModelId ?? "—"} }, { title: "状态", render: (_, item) => {modelStatusLabels[item.status] ?? item.status}{item.enabled ? 启用 : 停用} }, { title: "发布时间", dataIndex: "publishedAt", render: formatAdminDate }, { title: "操作", fixed: "right", render: (_, item) => {canWrite && } {canTest && } {canPublish && item.status !== "published" && } {canRollback && item.status !== "draft" && } , }, ]; return } onClick={() => openProvider()}>新增供应商 : null}> } onClick={() => openModel()} disabled={!providers.length} title={providers.length ? undefined : "请先新增供应商"}>新增模型 : null}> form={filterForm} layout="inline" style={{ rowGap: 8 }} onFinish={(values) => setFilters({ q: values.q?.trim() || undefined, status: values.status || undefined, })} >
providerForm.submit()} onCancel={() => { setProviderOpen(false); providerForm.resetFields(); }} destroyOnHidden> form={providerForm} layout="vertical" onFinish={saveProvider}> : null} modelForm.submit()} onCancel={() => setModelOpen(false)} destroyOnHidden> form={modelForm} layout="vertical" onFinish={saveModel}> ({ value: item.id, label: item.label ? `${item.label} (${item.id})` : item.id }))} /> ({ validator(_, value) { return value && !getFieldValue("enabled") ? Promise.reject(new Error("默认模型必须启用")) : Promise.resolve(); } })]}> setVersionAction(null)} onOk={() => void submitVersionAction()} destroyOnHidden > {versionAction?.action === "rollback" ? "确认将此历史版本恢复为新的已发布版本?" : "确认发布此模型版本?"} ; }