fix: charge style pro image generation
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-17 00:58:57 +08:00
parent 5ece717e31
commit ce5daaea45
2 changed files with 77 additions and 7 deletions

View File

@ -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}`

View File

@ -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();
});