import type { ModelCapabilityChoice } from "@/components/model-protocol-picker"; import { defaultModelCapabilityConfig, normalizeModelCapabilityConfig, type ModelCapabilityConfig } from "@/lib/model-capabilities"; import { modelProtocolSupportsTokenBilling, type ModelProtocolDefinition } from "@/lib/model-protocols"; import type { ChannelModel } from "@/services/api/wallet"; import type { ModelTag } from "@/lib/model-tags"; import { defaultPriceTier, legacyPriceTierToForm, priceTierToForm, type PriceTierFormValues } from "./channel-model-price-tier-form"; export type ChannelModelFormValues = { modelKey: string; providerModelKey?: string; displayName?: string; channelLabel?: string; tags: ModelTag[]; description?: string; icon?: string; capability: ModelCapabilityChoice; protocol?: string; priceTiers: PriceTierFormValues[]; enabled: boolean; capabilityConfig?: ModelCapabilityConfig; }; export type EditorSection = "identity" | "capabilities" | "pricing"; export function editorSectionForField(name: (string | number)[]): EditorSection { return name[0] === "priceTiers" ? "pricing" : name[0] === "capabilityConfig" ? "capabilities" : "identity"; } export function initialChannelModelValues(item: ChannelModel | null, protocols: ModelProtocolDefinition[]): ChannelModelFormValues { const capability = item?.capability || "text"; const protocol = item ? item.protocol : protocols.find((p) => p.capability === capability && p.enabled !== false)?.value; const upstreamModel = item?.providerModelKey || item?.modelKey || ""; return { modelKey: item?.modelKey || "", providerModelKey: upstreamModel, displayName: item?.displayName || "", channelLabel: item?.channelLabel || "", tags: item?.tags?.map((tag) => ({ ...tag })) || [], description: item?.description || "", icon: item?.icon || "", capability, protocol, priceTiers: item ? (item.priceTiers?.length ? item.priceTiers.map(priceTierToForm) : [legacyPriceTierToForm(item)]) : [defaultPriceTier()], enabled: item?.enabled ?? true, capabilityConfig: capability === "audio" ? undefined : normalizeModelCapabilityConfig(item?.capabilityConfig || defaultModelCapabilityConfig(protocol, upstreamModel)), }; } // Clear incompatible selectors, but never silently convert prices or billing units. export function changeChannelModelCapability(values: ChannelModelFormValues, protocols: ModelProtocolDefinition[]): ChannelModelFormValues { const capability = values.capability; const protocol = protocols.find((p) => p.value === values.protocol && p.capability === capability && p.enabled !== false)?.value || protocols.find((p) => p.capability === capability && p.enabled !== false)?.value; return { ...values, protocol, capabilityConfig: capability === "audio" ? undefined : defaultModelCapabilityConfig(protocol, values.providerModelKey?.trim() || values.modelKey.trim()), priceTiers: values.priceTiers.map((tier) => ({ ...tier, operation: "*", quality: "*", size: "*", resolution: "*", videoSeconds: 0, videoGenerateAudio: "*", imageCount: 0, })), }; } export function validateChannelModelProtocol(capability: string, protocol: string | undefined, protocols: ModelProtocolDefinition[]) { if (!protocols.some((p) => p.value === protocol && p.capability === capability && p.enabled !== false)) { throw new Error("请选择当前能力下已启用的请求协议"); } } export function validateChannelModelPrices(values: Pick) { const { capability, protocol, priceTiers } = values; if (!priceTiers?.length) throw new Error("请至少配置一个价格档"); if (priceTiers.filter((tier) => tier.matchMode === "default").length > 1) throw new Error("只能配置一个所有规格统一价格"); const operations: Record = { text: ["text_generation"], image: ["text_to_image", "image_to_image"], video: ["text_to_video", "image_to_video", "video_to_video"], audio: [], }; priceTiers.forEach((tier, index) => { const fail = (text: string) => { throw new Error(`价格档 ${index + 1}:${text}`); }; if (!["fixed_request", "per_second", "token"].includes(tier.billingMode)) fail("请选择计费方式"); if (tier.billingMode === "per_second" && capability !== "video") fail("按秒计费仅支持视频,请重新选择计费方式并核对价格"); if (tier.billingMode === "token" && !modelProtocolSupportsTokenBilling(capability, protocol)) fail("当前模型能力不支持 Token 计费,请重新选择计费方式并核对价格"); if (tier.matchMode === "advanced") { if (tier.operation && tier.operation !== "*" && !operations[capability]?.includes(tier.operation)) fail("生成方式与模型能力不匹配"); const specific = (value: string | undefined) => Boolean(value && value !== "*"); if (!(specific(tier.operation) || (capability === "image" && (specific(tier.quality) || specific(tier.size))) || (capability === "video" && specific(tier.resolution)))) fail("规格价格至少需要一个匹配条件;统一价格请选择默认价格"); } const prices = tier.billingMode !== "token" ? [tier.unitPrice] : capability === "video" ? [tier.outputTokenPrice] : [tier.inputTokenPrice, tier.outputTokenPrice, tier.cachedTokenPrice]; if (prices.some((price) => typeof price !== "number" || !Number.isFinite(price) || price < 0 || price > 1_000_000)) fail("积分价格必须是 0 到 1000000 之间的有效数值"); if (tier.costConfigured) { const costs = tier.billingMode !== "token" ? [tier.costUnitPrice] : capability === "video" ? [tier.costOutputTokenPrice] : [tier.costInputTokenPrice, tier.costOutputTokenPrice, tier.costCachedTokenPrice]; if (costs.some((price) => typeof price !== "number" || !Number.isFinite(price) || price < 0 || price > 1_000_000)) fail("积分成本价必须是 0 到 1000000 之间的有效数值"); } }); }