import { useRef, useState } from "react"; import { Alert, App, Button, Form, Input, Segmented, Switch, Tabs } from "antd"; import { AdminModal } from "@/pages/admin/ui/overlays"; import { FlaskConical, Plus } from "lucide-react"; import { ModelIconPicker } from "@/components/model-logo"; import { ModelIcon } from "@/components/model-picker"; import { ModelProtocolBrowser } from "@/components/model-protocol-browser"; import { ModelCapabilityEditor } from "@/components/model-capability-editor"; import { defaultModelCapabilityConfig, normalizeModelCapabilityConfig, type ModelCapabilityConfig } from "@/lib/model-capabilities"; import type { ModelProtocolDefinition } from "@/lib/model-protocols"; import { createAdminChannelModel, testAdminChannelModel, updateAdminChannelModel, type ChannelModel } from "@/services/api/wallet"; import type { ModelChannel } from "@/stores/use-config-store"; import { defaultPriceTier, normalizeUpstreamModelKey, priceTierPayloadFromForm } from "./channel-model-price-tier-form"; import { PriceTierFields } from "./channel-model-price-tier-fields"; import { changeChannelModelCapability, editorSectionForField, initialChannelModelValues, validateChannelModelPrices, validateChannelModelProtocol, type ChannelModelFormValues as FormValues, type EditorSection } from "./channel-model-editor-form"; export function ChannelModelEditor({ channel, editing, protocols, protocolLoading, protocolError, onRetryProtocols, onClose, onSaved, }: { channel: ModelChannel; editing: ChannelModel | null; protocols: ModelProtocolDefinition[]; protocolLoading: boolean; protocolError: string; onRetryProtocols: () => void; onClose: () => void; onSaved: () => Promise; }) { const { message, modal } = App.useApp(); const [form] = Form.useForm(); const [initialValues] = useState(() => initialChannelModelValues(editing, protocols)); const [activeSection, setActiveSection] = useState("identity"); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); const [configurationChanged, setConfigurationChanged] = useState(false); const busyRef = useRef(false); const dirtyRef = useRef(false); const modelCapability = Form.useWatch("capability", form); const modelProtocol = Form.useWatch("protocol", form); const modelKey = Form.useWatch("modelKey", form) || ""; const providerModelKey = Form.useWatch("providerModelKey", form) || ""; const capabilityConfig = Form.useWatch("capabilityConfig", form); const modelEnabled = Form.useWatch("enabled", form) !== false; const priceTiers = Form.useWatch("priceTiers", form) || []; const hasDefaultPriceTier = priceTiers.some((tier) => tier.matchMode === "default"); const tiersWithOwnUpstream = priceTiers.filter((tier) => tier.providerModelKey?.trim()); const modelUpstream = normalizeUpstreamModelKey(providerModelKey || modelKey); const busy = saving || testing; const requestClose = () => { if (busyRef.current) return; if (!dirtyRef.current) { onClose(); return; } modal.confirm({ title: "放弃未保存的修改?", content: "当前修改尚未保存,关闭后将丢弃。", okText: "放弃修改", cancelText: "继续编辑", onOk: onClose }); }; const handleFormValuesChange = (changed: Partial) => { dirtyRef.current = true; if (changed.capability) { form.setFieldsValue(changeChannelModelCapability(form.getFieldsValue(true), protocols)); setConfigurationChanged(true); } else if (changed.protocol) { form.setFieldValue("capabilityConfig", modelCapability === "audio" ? undefined : defaultModelCapabilityConfig(changed.protocol, providerModelKey.trim() || modelKey.trim())); setConfigurationChanged(true); } }; const validateEditor = async (names?: string[]): Promise => { busyRef.current = true; try { await form.validateFields(names); return form.getFieldsValue(true); } catch (error) { const fields = (error as { errorFields?: { name: (string | number)[] }[] }).errorFields; if (fields?.length) { setActiveSection(editorSectionForField(fields[0].name)); requestAnimationFrame(() => form.scrollToField(fields[0].name, { block: "center", focus: true })); message.warning("请检查标记的配置项后重试"); } else message.error(error instanceof Error ? error.message : "表单校验失败"); return undefined; } finally { busyRef.current = false; } }; const save = async () => { if (busyRef.current) return; const values = await validateEditor(); if (!values) return; const upstreamModel = values.providerModelKey?.trim() || values.modelKey.trim(); const capabilityConfig = values.capability === "text" || values.capability === "image" || values.capability === "video" ? normalizeModelCapabilityConfig(values.capabilityConfig || defaultModelCapabilityConfig(values.protocol, upstreamModel)) : undefined; busyRef.current = true; setSaving(true); try { const payload = { modelKey: values.modelKey.trim(), providerModelKey: upstreamModel, displayName: values.displayName?.trim() || values.modelKey.trim(), channelLabel: values.channelLabel?.trim() || "", description: values.description?.trim() || "", icon: values.icon?.trim() || "", capability: values.capability, protocol: values.protocol, priceTiers: values.priceTiers.map((tier) => priceTierPayloadFromForm(values.capability, tier, upstreamModel)), enabled: values.enabled !== false, capabilityConfig, }; if (editing) await updateAdminChannelModel(channel.id, editing.id, payload); else await createAdminChannelModel(channel.id, payload); try { await onSaved(); } catch { message.warning("模型已保存,但列表刷新失败,请手动刷新"); } onClose(); message.success(editing ? "模型配置已更新" : "模型已添加"); } catch (error) { message.error(error instanceof Error ? error.message : "保存模型失败"); } finally { busyRef.current = false; setSaving(false); } }; const testModel = async () => { if (busyRef.current) return; const values = await validateEditor(["modelKey", "providerModelKey", "capability", "protocol", "capabilityConfig"]); if (!values) return; const upstreamModel = values.providerModelKey?.trim() || values.modelKey.trim(); const capabilityConfig = values.capability === "text" || values.capability === "image" || values.capability === "video" ? normalizeModelCapabilityConfig(values.capabilityConfig || defaultModelCapabilityConfig(values.protocol, upstreamModel)) : undefined; busyRef.current = true; setTesting(true); try { const result = await testAdminChannelModel(channel.id, { modelKey: values.modelKey.trim(), providerModelKey: upstreamModel, capability: values.capability, protocol: values.protocol, capabilityConfig, }); message.success(`模型测试通过,耗时 ${(result.durationMs / 1000).toFixed(2)} 秒`); } catch (error) { message.error(error instanceof Error ? error.message : "模型测试失败"); } finally { busyRef.current = false; setTesting(false); } }; return ( {editing ? "编辑模型" : "新增模型"}

{channel.name} · {editing?.displayName || editing?.modelKey || "配置调用方式与用户积分价格"}

} width={1120} centered rootClassName="admin-modal-root admin-model-editor-modal" mask={{ closable: false }} keyboard={!busy} closable={!busy} onCancel={requestClose} flush styles={{ body: { minHeight: 0, flex: 1 }, header: { margin: 0 }, footer: { margin: 0 } }} footer={
{ dirtyRef.current = true; form.setFieldValue("enabled", checked); }} />
{modelEnabled ? "模型启用" : "模型停用"} 保存后生效
} >
setActiveSection(key as EditorSection)} animated={false} items={[ { key: "identity", label: "基本信息", forceRender: true, children: (
} placeholder="例如:seedance-2-5" />
{tiersWithOwnUpstream.length ? ( ) : null}
validateChannelModelProtocol(form.getFieldValue("capability"), value, protocols) }]}> {protocolError && ( 重试 } /> )}
), }, { key: "capabilities", label: "能力与参数", forceRender: true, children: (
{configurationChanged && }{" "} {modelCapability === "text" || modelCapability === "image" || modelCapability === "video" ? (
{ dirtyRef.current = true; form.setFieldValue("capabilityConfig", next); }} />
) : null} {modelCapability === "text" || modelCapability === "image" || modelCapability === "video" ? (
{ dirtyRef.current = true; form.setFieldValue("capabilityConfig", next); }} />
) : null} {modelCapability === "audio" && }
), }, { key: "pricing", label: "积分定价", forceRender: true, children: (
{configurationChanged && }{" "}
{ validateChannelModelPrices({ capability: form.getFieldValue("capability"), protocol: form.getFieldValue("protocol"), priceTiers: value }); }, }, ]} > {(fields, { add, remove }, { errors }) => (
{fields.map((field, index) => ( { dirtyRef.current = true; }} onRemove={() => remove(field.name)} /> ))}
)}
), }, ]} />
); } function SectionHeading({ title, description }: { title: string; description: string }) { return (

{title}

{description}

); } function CapabilityConfigField(_: { value?: ModelCapabilityConfig; onChange?: (value: ModelCapabilityConfig) => void }) { return null; } function EnabledConfigField(_: { value?: boolean; onChange?: (value: boolean) => void }) { return null; }