feat: split 3d model and world point costs
This commit is contained in:
parent
607fd3bc3e
commit
5c2ece9fb6
@ -7413,7 +7413,7 @@ export function AdminSettingsClient() {
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="pointsEnabled">启用积分消耗</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
开启后按模型单价和图片张数扣除积分
|
||||
开启后按不同功能配置的单价扣除积分
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
@ -7435,10 +7435,10 @@ export function AdminSettingsClient() {
|
||||
<div className="flex items-center justify-between rounded-lg border p-3">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="pointsBlockOnInsufficient">
|
||||
余额不足时阻止生图
|
||||
余额不足时阻止生成
|
||||
</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
开启后积分不足直接返回错误,不执行生图任务
|
||||
开启后积分不足直接返回错误,不执行生成任务
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
@ -7459,35 +7459,72 @@ export function AdminSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border p-3">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="points3dCost">3D生成单次积分消耗</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
作用于 3D 生成功能(文生3D / 图生3D /
|
||||
多视图3D),每次提交任务扣除一次。
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div className="rounded-lg border p-3">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="points3dModelCost">
|
||||
3D模型单次积分消耗
|
||||
</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
作用于 3D模型模式(文生3D / 图生3D / 多视图3D),每次提交任务扣除一次。
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 w-full max-w-xs">
|
||||
<Input
|
||||
id="points3dModelCost"
|
||||
min={0}
|
||||
type="number"
|
||||
value={
|
||||
siteSettings.pointsSystemConfig?.creditCostPer3DModel ??
|
||||
0
|
||||
}
|
||||
onChange={(e) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
pointsSystemConfig: {
|
||||
...siteSettings.pointsSystemConfig,
|
||||
creditCostPer3DModel: Math.max(
|
||||
0,
|
||||
Number.parseInt(e.target.value || "0", 10) || 0
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 w-full max-w-xs">
|
||||
<Input
|
||||
id="points3dCost"
|
||||
min={0}
|
||||
type="number"
|
||||
value={
|
||||
siteSettings.pointsSystemConfig?.creditCostPer3D ?? 0
|
||||
}
|
||||
onChange={(e) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
pointsSystemConfig: {
|
||||
...siteSettings.pointsSystemConfig,
|
||||
creditCostPer3D: Math.max(
|
||||
0,
|
||||
Number.parseInt(e.target.value || "0", 10) || 0
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div className="rounded-lg border p-3">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="points3dWorldCost">
|
||||
3D世界单次积分消耗
|
||||
</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
作用于 3D世界模式(文本 / 图片 / 多图 / 视频生成世界),每次提交任务扣除一次。
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 w-full max-w-xs">
|
||||
<Input
|
||||
id="points3dWorldCost"
|
||||
min={0}
|
||||
type="number"
|
||||
value={
|
||||
siteSettings.pointsSystemConfig?.creditCostPer3DWorld ??
|
||||
0
|
||||
}
|
||||
onChange={(e) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
pointsSystemConfig: {
|
||||
...siteSettings.pointsSystemConfig,
|
||||
creditCostPer3DWorld: Math.max(
|
||||
0,
|
||||
Number.parseInt(e.target.value || "0", 10) || 0
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
|
||||
@ -341,8 +341,8 @@ export async function POST(request: Request) {
|
||||
|
||||
const pointsConfig = await getPointsSystemConfig();
|
||||
const shouldChargePoints = shouldCharge3DPoints(taskType);
|
||||
const unitCost = Number.isFinite(Number(pointsConfig.creditCostPer3D))
|
||||
? Math.max(0, Math.round(Number(pointsConfig.creditCostPer3D)))
|
||||
const unitCost = Number.isFinite(Number(pointsConfig.creditCostPer3DModel))
|
||||
? Math.max(0, Math.round(Number(pointsConfig.creditCostPer3DModel)))
|
||||
: 0;
|
||||
|
||||
if (
|
||||
@ -399,7 +399,7 @@ export async function POST(request: Request) {
|
||||
amount: unitCost,
|
||||
reason: "model3d_generation",
|
||||
modelId: "tripo-3d",
|
||||
note: "3D生成消耗积分",
|
||||
note: "3D模型生成消耗积分",
|
||||
});
|
||||
|
||||
if (!consumeResult.ok) {
|
||||
@ -412,7 +412,7 @@ export async function POST(request: Request) {
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`[Points] Consumed ${unitCost} points for 3D generation. user=${session.user.id} task=${taskId} balance=${consumeResult.balance}`
|
||||
`[Points] Consumed ${unitCost} points for 3D model generation. user=${session.user.id} task=${taskId} balance=${consumeResult.balance}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -143,8 +143,8 @@ export async function POST(request: Request) {
|
||||
|
||||
const pointsConfig = await getPointsSystemConfig();
|
||||
const shouldChargePoints = shouldCharge3DPoints(mode);
|
||||
const unitCost = Number.isFinite(Number(pointsConfig.creditCostPer3D))
|
||||
? Math.max(0, Math.round(Number(pointsConfig.creditCostPer3D)))
|
||||
const unitCost = Number.isFinite(Number(pointsConfig.creditCostPer3DWorld))
|
||||
? Math.max(0, Math.round(Number(pointsConfig.creditCostPer3DWorld)))
|
||||
: 0;
|
||||
|
||||
if (
|
||||
@ -209,7 +209,7 @@ export async function POST(request: Request) {
|
||||
await consumeUserPoints({
|
||||
userId: session.user.id,
|
||||
amount: unitCost,
|
||||
reason: "model3d_generation",
|
||||
reason: "world3d_generation",
|
||||
modelId: "worldlabs-3d",
|
||||
note: "3D世界生成消耗积分",
|
||||
});
|
||||
|
||||
@ -22,7 +22,8 @@ const MEMBERSHIP_USER_OVERRIDES_KEY = "membership_user_plan_overrides";
|
||||
|
||||
function mapLedgerReasonLabel(reason: string) {
|
||||
if (reason === "image_generation") return "图片生成";
|
||||
if (reason === "model3d_generation") return "3D生成";
|
||||
if (reason === "model3d_generation") return "3D模型生成";
|
||||
if (reason === "world3d_generation") return "3D世界生成";
|
||||
if (reason === "video_generation") return "视频生成";
|
||||
if (reason === "deduct") return "管理员扣减";
|
||||
if (reason === "gift") return "管理员赠送";
|
||||
|
||||
@ -4,7 +4,8 @@ export const POINTS_PACKAGES_KEY = "points_packages";
|
||||
export type PointsSystemConfig = {
|
||||
enabled: boolean;
|
||||
blockOnInsufficient: boolean;
|
||||
creditCostPer3D: number;
|
||||
creditCostPer3DModel: number;
|
||||
creditCostPer3DWorld: number;
|
||||
};
|
||||
|
||||
export type PointsPackage = {
|
||||
@ -19,7 +20,8 @@ export type PointsPackage = {
|
||||
export const defaultPointsSystemConfig: PointsSystemConfig = {
|
||||
enabled: false,
|
||||
blockOnInsufficient: true,
|
||||
creditCostPer3D: 0,
|
||||
creditCostPer3DModel: 0,
|
||||
creditCostPer3DWorld: 0,
|
||||
};
|
||||
|
||||
export const defaultPointsPackages: PointsPackage[] = [
|
||||
@ -46,15 +48,25 @@ export function normalizePointsSystemConfig(value: unknown): PointsSystemConfig
|
||||
return defaultPointsSystemConfig;
|
||||
}
|
||||
|
||||
const raw = value as Partial<PointsSystemConfig>;
|
||||
const parsed3DCost = Number(raw.creditCostPer3D);
|
||||
const raw = value as Partial<PointsSystemConfig> & {
|
||||
creditCostPer3D?: number;
|
||||
};
|
||||
const normalizeCost = (input: unknown) => {
|
||||
const parsedValue = Number(input);
|
||||
return Number.isFinite(parsedValue) && parsedValue > 0
|
||||
? Math.min(100000, Math.round(parsedValue))
|
||||
: 0;
|
||||
};
|
||||
const legacy3DCost = normalizeCost(raw.creditCostPer3D);
|
||||
return {
|
||||
enabled: raw.enabled === true,
|
||||
blockOnInsufficient: raw.blockOnInsufficient !== false,
|
||||
creditCostPer3D:
|
||||
Number.isFinite(parsed3DCost) && parsed3DCost > 0
|
||||
? Math.min(100000, Math.round(parsed3DCost))
|
||||
: 0,
|
||||
creditCostPer3DModel: normalizeCost(
|
||||
raw.creditCostPer3DModel ?? legacy3DCost
|
||||
),
|
||||
creditCostPer3DWorld: normalizeCost(
|
||||
raw.creditCostPer3DWorld ?? legacy3DCost
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user