import "server-only"; import type { DiscountableImageGenConfigLike } from "@/lib/ai/image-model-pricing"; import { getImageCreditCostFromConfig, getImageModelCreditDiscountLabel, } from "@/lib/ai/image-model-pricing"; import { getEnabledAiModelsWithProvider } from "@/lib/db/queries"; export type StyleProModelEntry = Awaited< ReturnType >[number]; export type PublicStyleProModelOption = { id: string; modelId: string; displayName: string; avatarUrl: string | null; providerName: string; providerType: string; isDefault: boolean; unitCost: number; creditDiscountLabel: string | null; }; const VIDEO_PROVIDER_TYPES = new Set([ "grsai-video", "qianwen-video", "doubao-video", "ruxa-video", "volcano-video", "fal-video", "runway", ]); const VIDEO_MODEL_KEYWORDS = [ "/generate/video/", "video", "veo", "sora", "kling", "hailuo", "seedance", "runway", "gen-3", "gen-4", "gen 3", "gen 4", "wan-2.", "wan 2.", "t2v", "i2v", ]; function hasVideoGenerationConfig(value: unknown) { if (!value || typeof value !== "object") return false; const config = value as Record; return ( "creditCostPerVideo" in config || "creditCostPerVideoByDuration" in config || "creditCostPerVideoByResolution" in config || "videoDurationOptions" in config || "videoResolutionOptions" in config ); } function hasVideoCapabilities(value: unknown) { if (!value || typeof value !== "object") return false; const serialized = JSON.stringify(value).toLowerCase(); return serialized.includes("text-to-video") || serialized.includes("image-to-video"); } function isVideoLikeModel(entry: StyleProModelEntry) { if (VIDEO_PROVIDER_TYPES.has(entry.provider.type)) return true; if ( hasVideoGenerationConfig(entry.model.imageGenConfig) || hasVideoCapabilities(entry.model.capabilities) || hasVideoCapabilities(entry.model.defaultParams) ) { return true; } const searchable = [ entry.provider.type, entry.provider.name, entry.model.modelId, entry.model.displayName, entry.model.description, ] .filter(Boolean) .join(" ") .toLowerCase(); return VIDEO_MODEL_KEYWORDS.some((keyword) => searchable.includes(keyword)); } export function isStyleProImageModel(entry: StyleProModelEntry) { return Boolean(entry.model.supportsImageGen) && !isVideoLikeModel(entry); } export async function getStyleProImageModelEntries(options?: { includeHidden?: boolean; }) { const models = await getEnabledAiModelsWithProvider( false, options?.includeHidden === true ); return models.filter(isStyleProImageModel); } export function findStyleProImageModelByRef( entries: StyleProModelEntry[], modelRef?: string | null ) { const normalized = String(modelRef || "").trim(); if (!normalized) return null; return ( entries.find( ({ model }) => model.id === normalized || model.modelId === normalized ) || null ); } export function toPublicStyleProModelOption( entry: StyleProModelEntry ): PublicStyleProModelOption { const imageGenConfig = (entry.model.imageGenConfig || null) as DiscountableImageGenConfigLike | null; return { id: entry.model.id, modelId: entry.model.modelId, displayName: entry.model.displayName, avatarUrl: entry.model.avatarUrl || null, providerName: entry.provider.name, providerType: entry.provider.type, isDefault: entry.model.isDefault, unitCost: getImageCreditCostFromConfig(imageGenConfig), creditDiscountLabel: getImageModelCreditDiscountLabel(imageGenConfig), }; }