"use client"; import { useMemo, useState } from "react"; import { Search, RefreshCw, ChevronLeft, ChevronRight } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import type { AiModel } from "@/lib/db/schema"; import { is3DModelDescriptor, isVideoModelDescriptor, isImageGenerationModelDescriptor, } from "@/lib/ai/model-classification"; type Model = AiModel & { isAgentModel?: boolean; isMultimodal?: boolean }; type Provider = { id: string; name: string; type: string; isEnabled: boolean }; const selectClass = "h-10 rounded-md border bg-background px-3 text-sm"; export function ModelManagementList({ models, providers, error, loading, onEdit, onDelete, onRestore, onRefresh, onHealth, }: { models: Model[]; providers: Provider[]; error?: Error; loading: boolean; onEdit: (model: Model) => Promise; onDelete: (id: string) => void; onRestore: (model: Model) => void; onRefresh: () => void; onHealth?: (id: string) => void; }) { const [search, setSearch] = useState(""); const [provider, setProvider] = useState(""); const [status, setStatus] = useState("enabled"); const [kind, setKind] = useState(""); const [page, setPage] = useState(1); const [opening, setOpening] = useState(null); const rows = useMemo( () => models .map((model) => { const upstream = providers.find((p) => p.id === model.providerId); const descriptor = { ...model, providerType: upstream?.type }; const type = is3DModelDescriptor(descriptor) ? "3d" : isVideoModelDescriptor(descriptor) ? "video" : isImageGenerationModelDescriptor(descriptor) ? "image" : "chat"; return { model, upstream, type }; }) .filter(({ model, upstream, type }) => { const text = `${model.displayName} ${model.modelId} ${upstream?.name || ""}`.toLowerCase(); return ( text.includes(search.trim().toLowerCase()) && (!provider || model.providerId === provider) && (!kind || kind === type) && (status === "all" || (status === "enabled" && model.isEnabled) || (status === "disabled" && !model.isEnabled) || (status === "unhealthy" && model.healthStatus !== "healthy") || (status === "visible" && !model.isHidden)) ); }), [models, providers, search, provider, status, kind], ); const pages = Math.max(1, Math.ceil(rows.length / 25)); const currentPage = Math.min(page, pages); const change = (fn: () => void) => { fn(); setPage(1); }; return (
change(() => setSearch(e.target.value))} />

{loading ? "正在加载模型…" : `筛选结果 ${rows.length} 个 · 已启用 ${models.filter((m) => m.isEnabled).length} 个 · 全部 ${models.length} 个`}

第 {currentPage} / {pages} 页
{error ? (
模型加载失败。
) : loading ? (
) : (
{["模型", "上游 / 类型", "启用", "前台展示", "运行健康", "操作"].map((x) => ( ))} {rows .slice((currentPage - 1) * 25, currentPage * 25) .map(({ model, upstream, type }) => ( ))}
{x}
{model.displayName}
{model.modelId}
{model.isDefault && 默认模型} {model.isDefaultTextEditModel && 文字编辑默认} {model.isAgentModel && Agent} {model.isMultimodal && 多模态} {model.isSubModel && 子模型}
{upstream?.name || "未知上游"}
{{ image: "生图", video: "视频", chat: "对话", "3d": "3D / CAD" }[type]} {upstream && !upstream.isEnabled ? " · 上游已停用" : ""}
{model.isEnabled ? "已启用" : "已停用"} {model.isHidden || model.isSubModel ? "隐藏" : "可见"} {model.healthStatus === "auto_disabled" ? "自动停用" : model.healthStatus === "degraded" ? "观察中" : "正常"} {model.healthConsecutiveFailures > 0 && ( 连续失败 {model.healthConsecutiveFailures} 次 )} {model.healthLastError && (
查看最近原因

{model.healthLastError}

)}
{model.excludeFromHealthBan && "不自动封禁 "} {model.excludeFromFailover && "不参与迁移"}
{model.healthStatus === "auto_disabled" && ( )}
{rows.length === 0 && (

没有符合条件的模型。

)}
)}
每页 25 个 · 第 {currentPage} / {pages} 页
); }