import { createLogger } from "@/lib/observability/logger"; import { getModelGenerationCreditCost } from "@/lib/ai/image-model-pricing"; import { consumeUserPoints, getAiModelById, getEnabledAiModelsWithProvider, getPointsSystemConfig, getUserById, } from "@/lib/db/queries"; const chatApiLogger = createLogger("chat.billing"); const MODEL_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; export function getModelCreditCost( modelConfig: any, target: "image" | "video", options?: { duration?: number | null; imageSize?: string | null; resolution?: string | null; }, ): number { return getModelGenerationCreditCost(modelConfig, target, options); } export async function resolveBillingModelByReference(modelRef: string): Promise { if (!modelRef) return undefined; if (MODEL_UUID_REGEX.test(modelRef)) { return await getAiModelById({ id: modelRef }).catch(() => undefined); } const allModels = await getEnabledAiModelsWithProvider(true, true).catch(() => []); const matched = allModels.find( ({ model }: any) => model.id === modelRef || model.modelId === modelRef, ); return matched?.model; } export async function consumeGeneratedVideoPoints(params: { userId: string; chatId: string; messageId: string; modelRefs: Array; duration?: number | null; resolution?: string | null; }) { try { const pointsConfig = await getPointsSystemConfig(); if (!pointsConfig.enabled) return; const userData = await getUserById({ id: params.userId }).catch(() => undefined); if (userData?.isHighVip) { chatApiLogger.info(`[Points] Skip video points for VIP user=${params.userId}`); return; } let billingModel: any | undefined; for (const ref of params.modelRefs) { if (!ref) continue; const resolved = await resolveBillingModelByReference(ref); if (!resolved) continue; billingModel = resolved; if ( getModelCreditCost(resolved, "video", { duration: params.duration, resolution: params.resolution, }) > 0 ) { break; } } if (!billingModel) return; const unitCost = getModelCreditCost(billingModel, "video", { duration: params.duration, resolution: params.resolution, }); if (unitCost <= 0) return; const consumeResult = await consumeUserPoints({ userId: params.userId, amount: unitCost, reason: "video_generation", modelId: billingModel.id, chatId: params.chatId, messageId: params.messageId, note: `模型(${billingModel.displayName || billingModel.modelId || billingModel.id}) 生视频 1 条${params.duration ? `(${params.duration} 秒)` : ""}${params.resolution ? `(${params.resolution})` : ""}`, }); if (!consumeResult.ok) { chatApiLogger.warn(`[Points] Failed to consume video points. user=${params.userId} model=${billingModel.id} cost=${unitCost}`); } else if (consumeResult.skipped) { chatApiLogger.info(`[Points] Skipped ${unitCost} video points for VIP user=${params.userId} model=${billingModel.id}`); } else { chatApiLogger.info(`[Points] Consumed ${unitCost} points for video. balance=${consumeResult.balance}`); } } catch (error) { chatApiLogger.error("[Points] Failed to consume points after video generation:", error); } }