From c63f163b0e63bacc6381dd56fcc14c24d8c2d161 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 16 Apr 2026 15:09:27 +0800 Subject: [PATCH] fix: align video and enhance pro point pricing --- .../settings/admin-settings-client.tsx | 62 ++++++++++++++++--- app/(chat)/api/chat/route.ts | 1 + components/canvas/EnhanceProWorkspace.tsx | 2 +- components/enhance-pro-dialog.tsx | 2 +- lib/ai/krea-models.ts | 17 +++-- lib/ai/video-model-config.ts | 62 ++++++++++++++++--- lib/points/config.ts | 31 +++++----- 7 files changed, 138 insertions(+), 39 deletions(-) diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 95039602a..2cae5d26a 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -171,6 +171,9 @@ import { buildVideoDurationCostMap, getConfiguredVideoDurationOptions, normalizeVideoDurationOptions, + recommendedVideoDurationOptions, + recommendedVideoDurationReferenceCosts, + recommendedVideoResolutionBaseCosts, } from "@/lib/ai/video-model-config"; import type { AgentWorkflowUiConfig } from "@/types/agent"; @@ -421,6 +424,20 @@ function syncVideoDurationPricing(config: ImageGenConfig | undefined, durationOp }; } +function applyRecommendedVideoPricing(config: ImageGenConfig | undefined) { + return { + ...(config || createDefaultImageGenConfig()), + creditCostPerVideo: recommendedVideoResolutionBaseCosts["480p"], + videoDurationOptions: [...recommendedVideoDurationOptions], + creditCostPerVideoByDuration: { + ...recommendedVideoDurationReferenceCosts, + }, + creditCostPerVideoByResolution: { + ...recommendedVideoResolutionBaseCosts, + }, + }; +} + type AiModel = { id: string; providerId: string; @@ -5597,7 +5614,7 @@ export function AdminSettingsClient() { type="number" min="1" max={enhanceProConfig.maxUpscaleFactor} - step="0.5" + step="0.25" value={preset.imageScalingFactor} onChange={(e) => handleEnhanceProPresetChange( @@ -8301,7 +8318,7 @@ export function AdminSettingsClient() { 图像增强 Pro 积分规则
- Krea 官方文档没有公开精确单价公式,建议按输出分辨率档位、候选数量和模型倍率计算本地积分。 + 默认按基础 20 积分计算;启用放大后按放大倍数折算,例如 1.25x 为 25 积分、2x 为 40 积分。
- 可按放大倍率、分辨率档位或固定单价计算本地积分。 + 可按放大倍率、分辨率档位或固定单价计算本地积分;推荐使用“放大倍率”。
@@ -8372,7 +8389,7 @@ export function AdminSettingsClient() { 固定模式单次基础积分
- 仅在“固定单价”模式下生效,后续仍会叠加候选数量和模型倍率。 + 固定单价模式直接使用该值;放大倍率模式会用它作为基础价计算未配置的小数倍数。
@@ -8405,7 +8422,7 @@ export function AdminSettingsClient() {
- 开启后用户关闭“启用放大”时不扣除图像增强 Pro 积分。 + 关闭时即使用户不启用放大也按 1x 基础价扣积分。
- “放大倍率”计费模式下使用;小数倍率会匹配大于等于该倍率的最近档位。 + “放大倍率”计费模式下优先使用精确档位;未配置的小数倍率按基础价 × 倍数自动计算。
@@ -12007,6 +12024,34 @@ export function AdminSettingsClient() { {isVideoProviderForModelConfig && (
+
+
+
+ 推荐视频积分规则 +
+

+ 按 5/10/15 秒 = 1/1.5/2 倍,480p/720p/1080p 基础价 = + 100/200/250 积分计算。 +

+
+ +
+

- 配置后会优先按用户选择的秒数扣积分;未命中的秒数才回退到“每条视频积分消耗”。 + 如果同时配置了分辨率积分,系统会把最短秒数的积分作为 1 + 倍参考,其它秒数按比例乘分辨率基础价。

) : ( @@ -12155,7 +12201,7 @@ export function AdminSettingsClient() { )}

- 配置后会优先按用户选择的视频分辨率扣积分;未命中再回退到秒数或统一价格。 + 配置后按用户选择的视频分辨率取基础价,再叠加上方秒数倍率;未命中再回退到秒数或统一价格。

diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 168b98faf..aabde3bed 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -9064,6 +9064,7 @@ export async function POST(request: Request) { ); const modelCreditCostPerVideo = getModelCreditCost(currentModelConfig, "video", { duration: Number(effectiveVideoParams?.duration) || undefined, + resolution: String(effectiveVideoParams?.resolution || "").trim() || undefined, }); if (isImageGenerationModel) { diff --git a/components/canvas/EnhanceProWorkspace.tsx b/components/canvas/EnhanceProWorkspace.tsx index d2fdda464..1b602b39f 100644 --- a/components/canvas/EnhanceProWorkspace.tsx +++ b/components/canvas/EnhanceProWorkspace.tsx @@ -1163,7 +1163,7 @@ export function EnhanceProWorkspace({ ) ) } - step="0.5" + step="0.25" type="number" value={formState.image_scaling_factor} /> diff --git a/components/enhance-pro-dialog.tsx b/components/enhance-pro-dialog.tsx index 7e979f392..f640e7974 100644 --- a/components/enhance-pro-dialog.tsx +++ b/components/enhance-pro-dialog.tsx @@ -560,7 +560,7 @@ export function EnhanceProDialog({ type="number" min="1" max={config.maxUpscaleFactor} - step="0.5" + step="0.25" value={formState.image_scaling_factor} onChange={(event) => handleNumericField( diff --git a/lib/ai/krea-models.ts b/lib/ai/krea-models.ts index f272048d7..4b4cc10d3 100644 --- a/lib/ai/krea-models.ts +++ b/lib/ai/krea-models.ts @@ -1,3 +1,9 @@ +import { + recommendedVideoDurationOptions, + recommendedVideoDurationReferenceCosts, + recommendedVideoResolutionBaseCosts, +} from "@/lib/ai/video-model-config"; + export type KreaModelKind = "image" | "image-enhance" | "video"; export interface KreaModelPreset { @@ -212,10 +218,13 @@ function videoPreset( capabilities: createVideoCapabilities(modelId, fields), defaultParams: createDefaultParams("video", modelId, fields, requiredFields), imageGenConfig: { - creditCostPerVideo: 0, - videoDurationOptions: durationOptions, - creditCostPerVideoByDuration: {}, - creditCostPerVideoByResolution: {}, + creditCostPerVideo: recommendedVideoResolutionBaseCosts["480p"], + videoDurationOptions: + durationOptions.length > 0 + ? durationOptions + : [...recommendedVideoDurationOptions], + creditCostPerVideoByDuration: recommendedVideoDurationReferenceCosts, + creditCostPerVideoByResolution: recommendedVideoResolutionBaseCosts, enableLoadingPlaceholder: true, loadingColor: "blue", }, diff --git a/lib/ai/video-model-config.ts b/lib/ai/video-model-config.ts index 5ece8d1aa..0127db840 100644 --- a/lib/ai/video-model-config.ts +++ b/lib/ai/video-model-config.ts @@ -1,6 +1,18 @@ export type VideoDurationCostMap = Record; export type VideoResolutionCostMap = Record; +export const recommendedVideoDurationOptions = [5, 10, 15] as const; +export const recommendedVideoDurationReferenceCosts: VideoDurationCostMap = { + "5": 100, + "10": 150, + "15": 200, +}; +export const recommendedVideoResolutionBaseCosts: VideoResolutionCostMap = { + "480p": 100, + "720p": 200, + "1080p": 250, +}; + export type VideoImageGenConfigLike = { creditCostPerVideo?: number | null; videoDurationOptions?: number[] | null; @@ -131,25 +143,55 @@ export function getVideoCreditCostFromConfig( const resolutionCostMap = normalizeVideoResolutionCostMap( config?.creditCostPerVideoByResolution ); - if (normalizedResolution && resolutionCostMap) { - if (Object.prototype.hasOwnProperty.call(resolutionCostMap, normalizedResolution)) { - return resolutionCostMap[normalizedResolution] || 0; - } - } - const durationOptions = getConfiguredVideoDurationOptions(config); const durationCostMap = normalizeVideoDurationCostMap( config?.creditCostPerVideoByDuration, durationOptions ); + const durationCostEntries = durationCostMap + ? Object.entries(durationCostMap) + .map(([durationKey, cost]) => ({ + duration: normalizePositiveInteger(durationKey), + cost: Math.max(0, Math.round(Number(cost) || 0)), + })) + .filter((item): item is { duration: number; cost: number } => + item.duration !== null + ) + .sort((left, right) => left.duration - right.duration) + : []; + const getDurationCost = () => { + if (normalizedDuration === null || durationCostEntries.length === 0) return null; + const matched = + durationCostEntries.find((item) => item.duration === normalizedDuration) || + durationCostEntries.find((item) => normalizedDuration <= item.duration) || + durationCostEntries[durationCostEntries.length - 1] || + null; + return matched?.cost ?? null; + }; - if (normalizedDuration !== null && durationCostMap) { - const durationKey = String(normalizedDuration); - if (Object.prototype.hasOwnProperty.call(durationCostMap, durationKey)) { - return durationCostMap[durationKey] || 0; + if (normalizedResolution && resolutionCostMap) { + if (Object.prototype.hasOwnProperty.call(resolutionCostMap, normalizedResolution)) { + const resolutionBaseCost = resolutionCostMap[normalizedResolution] || 0; + const durationCost = getDurationCost(); + const baseDurationCost = + durationCostEntries.find((item) => item.cost > 0)?.cost ?? null; + + if (durationCost !== null && baseDurationCost && baseDurationCost > 0) { + return Math.max( + 0, + Math.round((resolutionBaseCost * durationCost) / baseDurationCost) + ); + } + + return resolutionBaseCost; } } + const durationCost = getDurationCost(); + if (durationCost !== null) { + return durationCost; + } + const fallbackCost = Number(config?.creditCostPerVideo ?? 0); return Number.isFinite(fallbackCost) ? Math.max(0, Math.round(fallbackCost)) : 0; } diff --git a/lib/points/config.ts b/lib/points/config.ts index 2fc5ddad0..ddd33f942 100644 --- a/lib/points/config.ts +++ b/lib/points/config.ts @@ -55,15 +55,16 @@ export const defaultPointsSystemConfig: PointsSystemConfig = { enhanceProPricing: { enabled: false, chargeMode: "scale_factor", - fixedCost: 45, - chargeOnlyWhenUpscaling: true, + fixedCost: 20, + chargeOnlyWhenUpscaling: false, batchSizeMultiplierEnabled: true, scaleFactorCosts: { - "1": 0, - "1.5": 15, - "2": 20, - "3": 35, - "4": 45, + "1": 20, + "1.25": 25, + "1.5": 30, + "2": 40, + "3": 60, + "4": 80, }, resolutionBuckets: [ { @@ -100,8 +101,8 @@ export const defaultPointsSystemConfig: PointsSystemConfig = { modelMultipliers: { Redefine: 1, Recovery: 1, - "Recovery V2": 1.1, - Reimagine: 1.25, + "Recovery V2": 1, + Reimagine: 1, }, }, }; @@ -316,6 +317,7 @@ export function calculateEnhanceProPointsEstimate( const batchSize = Math.max(1, Math.round(Number(params.batchSize) || 1)); const upscalingActivated = params.upscalingActivated === true; const scaleFactor = normalizeScaleFactor(params.scaleFactor ?? 1, 1); + const billingScaleFactor = upscalingActivated ? scaleFactor : 1; const batchMultiplier = pricing.batchSizeMultiplierEnabled ? batchSize : 1; const modelMultiplier = pricing.modelMultipliers[model] || defaultPointsSystemConfig.enhanceProPricing.modelMultipliers[model]; @@ -338,18 +340,16 @@ export function calculateEnhanceProPointsEstimate( .sort((left, right) => left.factor - right.factor); const scaleFactorCost = scaleFactorCostEntries.find( - (item) => Math.abs(item.factor - scaleFactor) < 0.001 - ) || - scaleFactorCostEntries.find((item) => scaleFactor <= item.factor) || - scaleFactorCostEntries[scaleFactorCostEntries.length - 1] || - null; + (item) => Math.abs(item.factor - billingScaleFactor) < 0.001 + ) || null; + const linearScaleFactorCost = Math.ceil(pricing.fixedCost * billingScaleFactor); const baseCost = pricing.chargeOnlyWhenUpscaling && !upscalingActivated ? 0 : pricing.chargeMode === "fixed" ? pricing.fixedCost : pricing.chargeMode === "scale_factor" - ? Math.max(0, Number(scaleFactorCost?.cost || 0)) + ? Math.max(0, Number(scaleFactorCost?.cost ?? linearScaleFactorCost)) : Math.max(0, Number(bucket?.cost || 0)); const rawCost = baseCost * batchMultiplier * modelMultiplier; const cost = baseCost > 0 ? Math.max(1, Math.ceil(rawCost)) : 0; @@ -362,6 +362,7 @@ export function calculateEnhanceProPointsEstimate( chargeOnlyWhenUpscaling: pricing.chargeOnlyWhenUpscaling, upscalingActivated, scaleFactor, + billingScaleFactor, megapixels, batchSize, batchMultiplier,