import { useMemo, useState, type FormEvent } from 'react'; import { Pencil, Plus, RotateCcw, Trash2 } from 'lucide-react'; import type { BaseModelCatalogItem, BaseModelUpsertRequest, CatalogProvider, PricingRuleSet, RuntimePolicySet, } from '@easyai-ai-gateway/contracts'; import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmDialog, FormDialog, FormItem, Label, Input, Select, Textarea } from '../../components/ui'; import type { LoadState } from '../../types'; import { BaseModelCapabilityEditor } from './BaseModelCapabilityEditor'; import { addCapabilityType, capabilitiesFromForm, capabilitiesToForm, createCapabilityEditorState, platformModelTypeDefinitions, removeCapabilityType, syncPrimaryCapability, type CapabilityEditorState, } from './base-model-capabilities'; import { ModelCatalogCard } from './ModelCatalogCard'; type ModelForm = { providerKey: string; canonicalModelKey: string; providerModelName: string; modelAlias: string; status: string; pricingRuleSetId: string; runtimePolicySetId: string; runtimePolicyOverrideJson: string; pricingVersion: string; capabilities: CapabilityEditorState; billingJson: string; rateLimitJson: string; metadataJson: string; }; const statuses = ['active', 'deprecated', 'hidden']; const fallbackTypes = platformModelTypeDefinitions.map((item) => item.key); function createEmptyForm(modelType = 'text_generate'): ModelForm { return { providerKey: '', canonicalModelKey: '', providerModelName: '', modelAlias: '', status: 'active', pricingRuleSetId: '', runtimePolicySetId: '', runtimePolicyOverrideJson: '{}', pricingVersion: '1', capabilities: createCapabilityEditorState(modelType), billingJson: '{}', rateLimitJson: '{}', metadataJson: '{}', }; } export function BaseModelCatalogPanel(props: { baseModels: BaseModelCatalogItem[]; message: string; pricingRuleSets: PricingRuleSet[]; providers: CatalogProvider[]; runtimePolicySets: RuntimePolicySet[]; state: LoadState; onDeleteBaseModel: (baseModelId: string) => Promise; onResetAllBaseModels: () => Promise; onResetBaseModel: (baseModelId: string) => Promise; onSaveBaseModel: (input: BaseModelUpsertRequest, baseModelId?: string) => Promise; }) { const [dialogOpen, setDialogOpen] = useState(false); const [editingId, setEditingId] = useState(''); const [form, setForm] = useState(() => createEmptyForm()); const [localError, setLocalError] = useState(''); const [query, setQuery] = useState(''); const [providerFilter, setProviderFilter] = useState('all'); const [typeFilter, setTypeFilter] = useState('all'); const [pendingDeleteModel, setPendingDeleteModel] = useState(null); const [pendingResetModel, setPendingResetModel] = useState(null); const [pendingResetAll, setPendingResetAll] = useState(false); const providerNames = useMemo( () => new Map(props.providers.map((item) => [item.providerKey, item.displayName])), [props.providers], ); const typeOptions = useMemo( () => Array.from(new Set([...fallbackTypes, ...props.baseModels.flatMap(readModelTypes)])), [props.baseModels], ); const providerOptions = useMemo( () => Array.from(new Set([...props.providers.map((item) => item.providerKey), ...props.baseModels.map((item) => item.providerKey)])), [props.baseModels, props.providers], ); const defaultPricingRuleSetId = useMemo(() => defaultRuleSetId(props.pricingRuleSets), [props.pricingRuleSets]); const defaultRuntimePolicySetId = useMemo(() => defaultRuntimePolicyId(props.runtimePolicySets), [props.runtimePolicySets]); const systemModelCount = useMemo(() => props.baseModels.filter(isSystemBaseModel).length, [props.baseModels]); const customizedSystemModelCount = useMemo( () => props.baseModels.filter((item) => isSystemBaseModel(item) && item.customizedAt).length, [props.baseModels], ); const filteredModels = useMemo(() => { const keyword = query.trim().toLowerCase(); return props.baseModels.filter((item) => { const matchesProvider = providerFilter === 'all' || item.providerKey === providerFilter; const matchesType = typeFilter === 'all' || readModelTypes(item).includes(typeFilter); const text = `${baseModelAlias(item)} ${item.providerModelName} ${item.canonicalModelKey}`.toLowerCase(); return matchesProvider && matchesType && (!keyword || text.includes(keyword)); }); }, [props.baseModels, providerFilter, query, typeFilter]); async function submit(event: FormEvent) { event.preventDefault(); setLocalError(''); try { await props.onSaveBaseModel(formToPayload(form), editingId || undefined); closeDialog(); } catch (err) { setLocalError(err instanceof Error ? err.message : '模型保存失败'); } } function openCreateDialog() { const providerKey = providerOptions[0] ?? ''; setEditingId(''); setForm({ ...createEmptyForm(), providerKey, canonicalModelKey: providerKey ? `${providerKey}:` : '', pricingRuleSetId: defaultPricingRuleSetId, runtimePolicySetId: defaultRuntimePolicySetId, }); setDialogOpen(true); } function editModel(model: BaseModelCatalogItem) { setEditingId(model.id); setForm({ ...modelToForm(model), pricingRuleSetId: model.pricingRuleSetId || defaultPricingRuleSetId, runtimePolicySetId: model.runtimePolicySetId || defaultRuntimePolicySetId, }); setDialogOpen(true); } function closeDialog() { setEditingId(''); setForm(createEmptyForm()); setLocalError(''); setDialogOpen(false); } async function deleteModel(model: BaseModelCatalogItem) { try { await props.onDeleteBaseModel(model.id); setPendingDeleteModel(null); if (editingId === model.id) closeDialog(); } catch (err) { setLocalError(err instanceof Error ? err.message : '模型删除失败'); } } async function resetModel(model: BaseModelCatalogItem) { try { await props.onResetBaseModel(model.id); setPendingResetModel(null); if (editingId === model.id) closeDialog(); } catch (err) { setLocalError(err instanceof Error ? err.message : '模型重置失败'); } } async function resetAllModels() { try { await props.onResetAllBaseModels(); setPendingResetAll(false); if (editingId) closeDialog(); } catch (err) { setLocalError(err instanceof Error ? err.message : '模型批量重置失败'); } } return (
基准模型库 {props.baseModels.length}

默认模型来自原 server-main integration-platform,保留模型类型、能力、图标、计费和限流元数据。

setQuery(event.target.value)} placeholder="搜索模型名称 / canonical key" />
{(props.message || localError) &&

{localError || props.message}

}
{filteredModels.map((model) => ( setPendingDeleteModel(model)} onEdit={() => editModel(model)} onReset={() => setPendingResetModel(model)} /> ))} {!filteredModels.length && ( 没有匹配的基准模型 )}
)} formClassName="baseModelForm" open={dialogOpen} title={editingId ? '编辑基准模型' : '新增基准模型'} onClose={closeDialog} onSubmit={submit} > setForm({ ...form, providerModelName: event.target.value })} /> setForm({ ...form, modelAlias: event.target.value })} /> setForm({ ...form, canonicalModelKey: event.target.value })} /> setForm({ ...form, pricingVersion: event.target.value })} /> setForm({ ...form, capabilities })} /> setForm({ ...form, billingJson: value })} /> setForm({ ...form, rateLimitJson: value })} /> setForm({ ...form, runtimePolicyOverrideJson: value })} /> setForm({ ...form, metadataJson: value })} /> setPendingDeleteModel(null)} onConfirm={() => pendingDeleteModel ? deleteModel(pendingDeleteModel) : undefined} /> setPendingResetModel(null)} onConfirm={() => pendingResetModel ? resetModel(pendingResetModel) : undefined} /> setPendingResetAll(false)} onConfirm={resetAllModels} />
); } function ModelCard(props: { model: BaseModelCatalogItem; pricingRuleSetName: string; providerName?: string; runtimePolicySetName: string; onDelete: () => void; onEdit: () => void; onReset: () => void; }) { const types = readModelTypes(props.model); const chips = [...types.slice(0, 5), ...(types.length > 5 ? [`+${types.length - 5}`] : [])]; const isSystem = isSystemBaseModel(props.model); return ( {isSystem && ( )} )} badges={[ {props.model.status}, {baseModelCatalogTypeLabel(props.model)}, ...(isSystem && props.model.customizedAt ? [已修改] : []), ]} chips={chips} iconPath={readModelIconPath(props.model)} iconText={props.model.providerKey} meta={[props.providerName ?? props.model.providerKey, props.pricingRuleSetName, props.runtimePolicySetName, props.model.canonicalModelKey]} subtitle={props.model.providerModelName} title={baseModelAlias(props.model)} /> ); } function JsonField(props: { label: string; value: string; onChange: (value: string) => void }) { return (