From ce5daaea45c94c54b4080f1d5a824b444f6b8576 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 17 May 2026 00:58:57 +0800 Subject: [PATCH] fix: charge style pro image generation --- app/api/style-pro/generate/route.ts | 60 +++++++++++++++++++++++++--- app/api/user/account-center/route.ts | 24 ++++++++++- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/app/api/style-pro/generate/route.ts b/app/api/style-pro/generate/route.ts index c3c0be0f5..ea887f50c 100644 --- a/app/api/style-pro/generate/route.ts +++ b/app/api/style-pro/generate/route.ts @@ -1,8 +1,10 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { auth } from "@/app/(auth)/auth"; +import { getModelGenerationCreditCost } from "@/lib/ai/image-model-pricing"; import { generateCanvasImage } from "@/lib/canvas/generate-image"; import { getSystemSetting } from "@/lib/db/queries"; +import { prepareFeaturePointsCharge } from "@/lib/points/feature-charge"; import { findStyleProImageModelByRef, getStyleProImageModelEntries, @@ -194,12 +196,21 @@ export async function POST(request: Request) { ); } - let selectedModelRef = config.modelId || undefined; + const imageModelEntries = await getStyleProImageModelEntries({ + includeHidden: false, + }); + const configuredDefaultModel = findStyleProImageModelByRef( + imageModelEntries, + config.modelId + ); + let selectedModel = + configuredDefaultModel || + imageModelEntries.find((entry) => entry.model.isDefault) || + imageModelEntries[0] || + null; + if (body.modelId) { - const imageModelEntries = await getStyleProImageModelEntries({ - includeHidden: false, - }); - const selectedModel = findStyleProImageModelByRef( + selectedModel = findStyleProImageModelByRef( imageModelEntries, body.modelId ); @@ -209,7 +220,26 @@ export async function POST(request: Request) { { status: 400 } ); } - selectedModelRef = selectedModel.model.id; + } + if (!selectedModel) { + return NextResponse.json( + { success: false, error: "暂无可用的风格化 PRO 生图模型" }, + { status: 400 } + ); + } + const selectedModelRef = selectedModel.model.id; + const unitCost = getModelGenerationCreditCost(selectedModel.model, "image"); + const pointsCharge = await prepareFeaturePointsCharge({ + userId: session.user.id, + unitCost, + reason: "style_pro_generation", + modelId: selectedModel.model.id, + note: `风格化 PRO 生图消耗积分(${selectedModel.model.displayName})`, + }); + if (!pointsCharge.ok) { + return NextResponse.json(pointsCharge.payload, { + status: pointsCharge.status, + }); } const prompt = composePrompt({ @@ -239,6 +269,23 @@ export async function POST(request: Request) { { status: 502 } ); } + let chargedPoints = 0; + if (pointsCharge.chargeable) { + try { + const consumeResult = await pointsCharge.consume(); + if (consumeResult?.ok && !consumeResult.skipped) { + chargedPoints = pointsCharge.unitCost; + } else if (!consumeResult?.ok) { + console.warn("[Style Pro Generate] Failed to consume points:", { + userId: session.user.id, + modelId: selectedModel.model.id, + unitCost: pointsCharge.unitCost, + }); + } + } catch (pointsError) { + console.error("[Style Pro Generate] Points consume failed:", pointsError); + } + } return NextResponse.json({ success: true, @@ -247,6 +294,7 @@ export async function POST(request: Request) { thumbnailUrl: image.thumbnailUrl || image.originalUrl, model: result.model, modelRef: result.modelRef, + chargedPoints, selections: selectionResult.normalizedSelections, optionLabels: selectionResult.selectedOptions.map( ({ category, option }) => `${category.title}:${option.label}` diff --git a/app/api/user/account-center/route.ts b/app/api/user/account-center/route.ts index 3f8338dda..ce6531d9d 100644 --- a/app/api/user/account-center/route.ts +++ b/app/api/user/account-center/route.ts @@ -27,6 +27,7 @@ const WORLD3D_LEDGER_NOTE_PATTERN = /^3D世界生成消耗积分((.+?))$/; function mapLedgerReasonLabel(reason: string) { if (reason === "image_generation") return "图片生成"; + if (reason === "style_pro_generation") return "风格化 PRO"; if (reason === "model3d_generation") return "3D模型生成"; if (reason === "model3d_task") return "3D模型任务"; if (reason === "model3d_task_refund") return "3D模型任务失败退回"; @@ -233,8 +234,29 @@ export async function GET() { status: "completed", invoice: "-", })); + const pointsBillingRecords = ledger + .filter((item) => item.reason !== "gift" && item.reason !== "recharge") + .map((item) => ({ + id: item.id, + date: item.createdAt?.toISOString() || new Date().toISOString(), + category: formatLedgerDetail({ + reason: item.reason, + note: item.note, + modelId: item.modelId, + tripoModels: threeDModelAliasConfig.tripoModels, + worldModels: threeDModelAliasConfig.worldModels, + tripoTaskDisplayNamesByTaskId: model3dTaskDisplayNamesByTaskId, + }), + amount: item.amount, + status: "completed", + invoice: "-", + })); - const billingRecords = [...rechargeRecords, ...giftedRecords].sort((a, b) => { + const billingRecords = [ + ...rechargeRecords, + ...giftedRecords, + ...pointsBillingRecords, + ].sort((a, b) => { return new Date(b.date).getTime() - new Date(a.date).getTime(); });