Use server enterprise video pricing
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import { describe, expect, it } from "../test/testHarness";
|
import { describe, expect, it } from "../test/testHarness";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
normalizeEnterpriseVideoPricingConfig,
|
||||||
normalizePublicModelPrice,
|
normalizePublicModelPrice,
|
||||||
normalizePublicModelPrices,
|
normalizePublicModelPrices,
|
||||||
|
normalizePublicPricingPayload,
|
||||||
} from "./publicPricingClient";
|
} from "./publicPricingClient";
|
||||||
|
|
||||||
describe("publicPricingClient", () => {
|
describe("publicPricingClient", () => {
|
||||||
@@ -69,4 +71,72 @@ describe("publicPricingClient", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes public pricing payloads with model prices and enterprise video pricing", () => {
|
||||||
|
expect(
|
||||||
|
normalizePublicPricingPayload({
|
||||||
|
modelPrices: [
|
||||||
|
{
|
||||||
|
modelKey: "qwen-turbo",
|
||||||
|
pricingType: "token",
|
||||||
|
inputPriceMills: 2,
|
||||||
|
outputPriceMills: 6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enterpriseVideoPricing: {
|
||||||
|
currency: "CNY",
|
||||||
|
creditsPerCny: 100,
|
||||||
|
billingUnit: "per_second",
|
||||||
|
defaultResolution: "1080P",
|
||||||
|
resolutions: ["720P", "1080P"],
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: "happyhorse",
|
||||||
|
modelIncludes: ["happyhorse"],
|
||||||
|
rates: { "720P": 0.72, "1080P": 1.28 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
modelPrices: [
|
||||||
|
{
|
||||||
|
id: undefined,
|
||||||
|
modelKey: "qwen-turbo",
|
||||||
|
displayName: undefined,
|
||||||
|
category: undefined,
|
||||||
|
pricingType: "token",
|
||||||
|
inputPriceMills: 2,
|
||||||
|
outputPriceMills: 6,
|
||||||
|
flatPriceMills: null,
|
||||||
|
currency: "CNY",
|
||||||
|
enabled: true,
|
||||||
|
createdAt: undefined,
|
||||||
|
updatedAt: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enterpriseVideoPricing: {
|
||||||
|
currency: "CNY",
|
||||||
|
creditsPerCny: 100,
|
||||||
|
billingUnit: "per_second",
|
||||||
|
defaultResolution: "1080P",
|
||||||
|
resolutions: ["720P", "1080P"],
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: "happyhorse",
|
||||||
|
modelIncludes: ["happyhorse"],
|
||||||
|
rates: { "720P": 0.72, "1080P": 1.28 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects malformed enterprise video pricing configs", () => {
|
||||||
|
expect(
|
||||||
|
normalizeEnterpriseVideoPricingConfig({
|
||||||
|
rules: [{ id: "broken", modelIncludes: [], rates: {} }],
|
||||||
|
}),
|
||||||
|
).toEqual(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { isOptionalApiRouteMissing } from "./apiErrorUtils";
|
import { isOptionalApiRouteMissing } from "./apiErrorUtils";
|
||||||
import { isRecord, serverRequest } from "./serverConnection";
|
import { isRecord, serverRequest } from "./serverConnection";
|
||||||
|
import type { EnterpriseVideoPricingConfig, EnterpriseVideoPricingRule } from "../utils/enterpriseVideoPolicy";
|
||||||
|
|
||||||
export interface PublicModelPrice {
|
export interface PublicModelPrice {
|
||||||
id?: number | string;
|
id?: number | string;
|
||||||
@@ -16,6 +17,11 @@ export interface PublicModelPrice {
|
|||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PublicPricingPayload {
|
||||||
|
modelPrices: PublicModelPrice[];
|
||||||
|
enterpriseVideoPricing: EnterpriseVideoPricingConfig | null;
|
||||||
|
}
|
||||||
|
|
||||||
function readString(
|
function readString(
|
||||||
record: Record<string, unknown>,
|
record: Record<string, unknown>,
|
||||||
keys: string[],
|
keys: string[],
|
||||||
@@ -62,6 +68,51 @@ function readBoolean(
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readStringArray(record: Record<string, unknown>, keys: string[]): string[] {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = record[key];
|
||||||
|
if (!Array.isArray(value)) continue;
|
||||||
|
return value
|
||||||
|
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRateMap(raw: unknown): Record<string, number> | null {
|
||||||
|
if (!isRecord(raw)) return null;
|
||||||
|
const result: Record<string, number> = {};
|
||||||
|
for (const [key, value] of Object.entries(raw)) {
|
||||||
|
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
||||||
|
if (Number.isFinite(parsed) && parsed >= 0) result[key] = parsed;
|
||||||
|
}
|
||||||
|
return Object.keys(result).length ? result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeEnterpriseVideoPricingRule(raw: unknown): EnterpriseVideoPricingRule | null {
|
||||||
|
if (!isRecord(raw)) return null;
|
||||||
|
const id = readString(raw, ["id", "key", "name"]);
|
||||||
|
const modelIncludes = readStringArray(raw, ["modelIncludes", "model_includes", "modelPatterns", "model_patterns"]);
|
||||||
|
const rates = normalizeRateMap(raw.rates);
|
||||||
|
if (!id || modelIncludes.length === 0 || !rates) return null;
|
||||||
|
|
||||||
|
const when = isRecord(raw.when)
|
||||||
|
? {
|
||||||
|
...(typeof raw.when.muted === "boolean" ? { muted: raw.when.muted } : {}),
|
||||||
|
...(typeof raw.when.hasReferenceVideo === "boolean"
|
||||||
|
? { hasReferenceVideo: raw.when.hasReferenceVideo }
|
||||||
|
: {}),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
modelIncludes,
|
||||||
|
...(when && Object.keys(when).length ? { when } : {}),
|
||||||
|
rates,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizePublicModelPrice(
|
export function normalizePublicModelPrice(
|
||||||
raw: unknown,
|
raw: unknown,
|
||||||
): PublicModelPrice | null {
|
): PublicModelPrice | null {
|
||||||
@@ -107,6 +158,10 @@ export function normalizePublicModelPrices(
|
|||||||
? payload
|
? payload
|
||||||
: isRecord(payload) && Array.isArray(payload.prices)
|
: isRecord(payload) && Array.isArray(payload.prices)
|
||||||
? payload.prices
|
? payload.prices
|
||||||
|
: isRecord(payload) && Array.isArray(payload.modelPrices)
|
||||||
|
? payload.modelPrices
|
||||||
|
: isRecord(payload) && Array.isArray(payload.model_prices)
|
||||||
|
? payload.model_prices
|
||||||
: isRecord(payload) && Array.isArray(payload.models)
|
: isRecord(payload) && Array.isArray(payload.models)
|
||||||
? payload.models
|
? payload.models
|
||||||
: [];
|
: [];
|
||||||
@@ -116,26 +171,66 @@ export function normalizePublicModelPrices(
|
|||||||
.filter((item): item is PublicModelPrice => Boolean(item));
|
.filter((item): item is PublicModelPrice => Boolean(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedPrices: PublicModelPrice[] | null = null;
|
export function normalizeEnterpriseVideoPricingConfig(raw: unknown): EnterpriseVideoPricingConfig | null {
|
||||||
|
if (!isRecord(raw)) return null;
|
||||||
|
const rules = Array.isArray(raw.rules)
|
||||||
|
? raw.rules
|
||||||
|
.map((item) => normalizeEnterpriseVideoPricingRule(item))
|
||||||
|
.filter((item): item is EnterpriseVideoPricingRule => Boolean(item))
|
||||||
|
: [];
|
||||||
|
if (rules.length === 0) return null;
|
||||||
|
|
||||||
|
const creditsPerCny = readNumber(raw, ["creditsPerCny", "credits_per_cny"]);
|
||||||
|
const defaultResolution = readString(raw, ["defaultResolution", "default_resolution"]);
|
||||||
|
const billingUnit = readString(raw, ["billingUnit", "billing_unit"]);
|
||||||
|
const currency = readString(raw, ["currency"]);
|
||||||
|
const resolutions = readStringArray(raw, ["resolutions", "supportedResolutions", "supported_resolutions"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(currency ? { currency } : {}),
|
||||||
|
...(creditsPerCny !== null ? { creditsPerCny } : {}),
|
||||||
|
...(billingUnit ? { billingUnit } : {}),
|
||||||
|
...(defaultResolution ? { defaultResolution } : {}),
|
||||||
|
...(resolutions.length ? { resolutions } : {}),
|
||||||
|
rules,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePublicPricingPayload(payload: unknown): PublicPricingPayload {
|
||||||
|
const enterpriseVideoPricingRaw =
|
||||||
|
isRecord(payload) && (payload.enterpriseVideoPricing ?? payload.enterprise_video_pricing);
|
||||||
|
|
||||||
|
return {
|
||||||
|
modelPrices: normalizePublicModelPrices(payload),
|
||||||
|
enterpriseVideoPricing: normalizeEnterpriseVideoPricingConfig(enterpriseVideoPricingRaw),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedPricing: PublicPricingPayload | null = null;
|
||||||
let pricesRouteMissing = false;
|
let pricesRouteMissing = false;
|
||||||
|
|
||||||
export const publicPricingClient = {
|
export const publicPricingClient = {
|
||||||
async getPrices(): Promise<PublicModelPrice[]> {
|
async getPricing(): Promise<PublicPricingPayload> {
|
||||||
if (cachedPrices) return cachedPrices;
|
if (cachedPricing) return cachedPricing;
|
||||||
if (pricesRouteMissing) return [];
|
if (pricesRouteMissing) return { modelPrices: [], enterpriseVideoPricing: null };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = await serverRequest<unknown>("prices", {
|
const payload = await serverRequest<unknown>("prices", {
|
||||||
fallbackMessage: "Model prices request failed",
|
fallbackMessage: "Model prices request failed",
|
||||||
});
|
});
|
||||||
cachedPrices = normalizePublicModelPrices(payload);
|
cachedPricing = normalizePublicPricingPayload(payload);
|
||||||
return cachedPrices;
|
return cachedPricing;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isOptionalApiRouteMissing(error)) {
|
if (isOptionalApiRouteMissing(error)) {
|
||||||
pricesRouteMissing = true;
|
pricesRouteMissing = true;
|
||||||
return [];
|
return { modelPrices: [], enterpriseVideoPricing: null };
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getPrices(): Promise<PublicModelPrice[]> {
|
||||||
|
const pricing = await publicPricingClient.getPricing();
|
||||||
|
return pricing.modelPrices;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,7 +72,11 @@ import {
|
|||||||
import { isViduModel } from "../../utils/viduRouting";
|
import { isViduModel } from "../../utils/viduRouting";
|
||||||
import { isPixverseModel } from "../../utils/pixverseRouting";
|
import { isPixverseModel } from "../../utils/pixverseRouting";
|
||||||
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
|
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
|
||||||
import { calculateEnterpriseVideoCredits, ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
|
import {
|
||||||
|
calculateEnterpriseVideoCredits,
|
||||||
|
ENTERPRISE_DEFAULT_VIDEO_MODEL,
|
||||||
|
type EnterpriseVideoPricingConfig,
|
||||||
|
} from "../../utils/enterpriseVideoPolicy";
|
||||||
import { resolveTextTokenCreditRate } from "../../utils/modelPricing";
|
import { resolveTextTokenCreditRate } from "../../utils/modelPricing";
|
||||||
import {
|
import {
|
||||||
getImageQualityOptionsForContext,
|
getImageQualityOptionsForContext,
|
||||||
@@ -408,6 +412,7 @@ function WorkbenchPage({
|
|||||||
|
|
||||||
const [chatModel, setChatModel] = useState(CHAT_MODEL_OPTIONS[0].value);
|
const [chatModel, setChatModel] = useState(CHAT_MODEL_OPTIONS[0].value);
|
||||||
const [modelPrices, setModelPrices] = useState<PublicModelPrice[]>([]);
|
const [modelPrices, setModelPrices] = useState<PublicModelPrice[]>([]);
|
||||||
|
const [enterpriseVideoPricing, setEnterpriseVideoPricing] = useState<EnterpriseVideoPricingConfig | null>(null);
|
||||||
const [thinkingSpeed, setThinkingSpeed] = useState(THINKING_SPEED_OPTIONS[0].value);
|
const [thinkingSpeed, setThinkingSpeed] = useState(THINKING_SPEED_OPTIONS[0].value);
|
||||||
const [thinkingDepth, setThinkingDepth] = useState(THINKING_DEPTH_OPTIONS[0].value);
|
const [thinkingDepth, setThinkingDepth] = useState(THINKING_DEPTH_OPTIONS[0].value);
|
||||||
|
|
||||||
@@ -415,12 +420,16 @@ function WorkbenchPage({
|
|||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
publicPricingClient
|
publicPricingClient
|
||||||
.getPrices()
|
.getPricing()
|
||||||
.then((prices) => {
|
.then((pricing) => {
|
||||||
if (!cancelled) setModelPrices(prices);
|
if (cancelled) return;
|
||||||
|
setModelPrices(pricing.modelPrices);
|
||||||
|
setEnterpriseVideoPricing(pricing.enterpriseVideoPricing);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!cancelled) setModelPrices([]);
|
if (cancelled) return;
|
||||||
|
setModelPrices([]);
|
||||||
|
setEnterpriseVideoPricing(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -566,7 +575,7 @@ function WorkbenchPage({
|
|||||||
durationSeconds,
|
durationSeconds,
|
||||||
muted: false,
|
muted: false,
|
||||||
hasReferenceVideo: referenceItems.some((item) => item.kind === "video"),
|
hasReferenceVideo: referenceItems.some((item) => item.kind === "video"),
|
||||||
});
|
}, enterpriseVideoPricing || undefined);
|
||||||
return {
|
return {
|
||||||
label: `预计 ${formatCreditValue(credits)} 积分`,
|
label: `预计 ${formatCreditValue(credits)} 积分`,
|
||||||
title: `${activeModel},${videoQualityLabel},${durationSeconds} 秒,预计 ${formatCreditValue(credits)} 积分`,
|
title: `${activeModel},${videoQualityLabel},${durationSeconds} 秒,预计 ${formatCreditValue(credits)} 积分`,
|
||||||
@@ -589,6 +598,7 @@ function WorkbenchPage({
|
|||||||
activeModel,
|
activeModel,
|
||||||
activeModelValue,
|
activeModelValue,
|
||||||
imageSettingsSummary,
|
imageSettingsSummary,
|
||||||
|
enterpriseVideoPricing,
|
||||||
referenceItems,
|
referenceItems,
|
||||||
selectedChatTokenRate,
|
selectedChatTokenRate,
|
||||||
videoDuration,
|
videoDuration,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from "../test/testHarness";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
calculateEnterpriseVideoCredits,
|
calculateEnterpriseVideoCredits,
|
||||||
|
type EnterpriseVideoPricingConfig,
|
||||||
getEnterpriseVideoCreditRate,
|
getEnterpriseVideoCreditRate,
|
||||||
normalizeEnterpriseResolution,
|
normalizeEnterpriseResolution,
|
||||||
} from "./enterpriseVideoPolicy";
|
} from "./enterpriseVideoPolicy";
|
||||||
@@ -45,4 +46,40 @@ describe("enterpriseVideoPolicy", () => {
|
|||||||
}),
|
}),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses server-provided pricing config before fallback pricing", () => {
|
||||||
|
const serverPricing: EnterpriseVideoPricingConfig = {
|
||||||
|
creditsPerCny: 100,
|
||||||
|
defaultResolution: "1080P",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: "happyhorse-server",
|
||||||
|
modelIncludes: ["happyhorse"],
|
||||||
|
rates: { "720P": 2, "1080P": 3 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getEnterpriseVideoCreditRate(
|
||||||
|
{
|
||||||
|
model: "happyhorse-1.0",
|
||||||
|
resolution: "1080P",
|
||||||
|
durationSeconds: 5,
|
||||||
|
},
|
||||||
|
serverPricing,
|
||||||
|
),
|
||||||
|
).toBe(3);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
calculateEnterpriseVideoCredits(
|
||||||
|
{
|
||||||
|
model: "happyhorse-1.0",
|
||||||
|
resolution: "1080P",
|
||||||
|
durationSeconds: 5,
|
||||||
|
},
|
||||||
|
serverPricing,
|
||||||
|
),
|
||||||
|
).toBe(1500);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,50 +50,119 @@ export interface EnterpriseVideoPricingInput {
|
|||||||
hasReferenceVideo?: boolean;
|
hasReferenceVideo?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EnterpriseVideoPricingRule {
|
||||||
|
id: string;
|
||||||
|
modelIncludes: string[];
|
||||||
|
when?: {
|
||||||
|
muted?: boolean;
|
||||||
|
hasReferenceVideo?: boolean;
|
||||||
|
};
|
||||||
|
rates: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnterpriseVideoPricingConfig {
|
||||||
|
currency?: string;
|
||||||
|
creditsPerCny?: number;
|
||||||
|
billingUnit?: "per_second" | string;
|
||||||
|
defaultResolution?: string;
|
||||||
|
resolutions?: string[];
|
||||||
|
rules: EnterpriseVideoPricingRule[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FALLBACK_ENTERPRISE_VIDEO_PRICING_CONFIG: EnterpriseVideoPricingConfig = {
|
||||||
|
currency: "CNY",
|
||||||
|
creditsPerCny: CREDITS_PER_CNY,
|
||||||
|
billingUnit: "per_second",
|
||||||
|
defaultResolution: ENTERPRISE_DEFAULT_VIDEO_RESOLUTION,
|
||||||
|
resolutions: ["720P", "1080P"],
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: "happyhorse",
|
||||||
|
modelIncludes: ["happyhorse"],
|
||||||
|
rates: { "720P": 0.72, "1080P": 1.28 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wanxiang-i2v",
|
||||||
|
modelIncludes: ["wan2.7-i2v", "wanxiang"],
|
||||||
|
rates: { "720P": 0.6, "1080P": 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wan-animate-s2v",
|
||||||
|
modelIncludes: ["animate-mix", "s2v"],
|
||||||
|
rates: { "720P": 0.6, "1080P": 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kling-muted-reference",
|
||||||
|
modelIncludes: ["kling"],
|
||||||
|
when: { muted: true, hasReferenceVideo: true },
|
||||||
|
rates: { "720P": 0.9, "1080P": 1.2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kling-muted",
|
||||||
|
modelIncludes: ["kling"],
|
||||||
|
when: { muted: true, hasReferenceVideo: false },
|
||||||
|
rates: { "720P": 0.6, "1080P": 0.8 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kling-default",
|
||||||
|
modelIncludes: ["kling"],
|
||||||
|
rates: { "720P": 0.9, "1080P": 1.2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vidu",
|
||||||
|
modelIncludes: ["vidu"],
|
||||||
|
rates: { "720P": 0.6, "1080P": 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pixverse",
|
||||||
|
modelIncludes: ["pixverse"],
|
||||||
|
rates: { "720P": 0.6, "1080P": 1 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export function normalizeEnterpriseResolution(value: string): "720P" | "1080P" {
|
export function normalizeEnterpriseResolution(value: string): "720P" | "1080P" {
|
||||||
return String(value || "").toUpperCase() === "720P" ? "720P" : "1080P";
|
return String(value || "").toUpperCase() === "720P" ? "720P" : "1080P";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEnterpriseVideoCreditRate(input: EnterpriseVideoPricingInput): number {
|
function enterpriseVideoPricingRuleMatches(
|
||||||
|
rule: EnterpriseVideoPricingRule,
|
||||||
|
input: EnterpriseVideoPricingInput,
|
||||||
|
model: string,
|
||||||
|
): boolean {
|
||||||
|
if (!rule.modelIncludes.some((pattern) => model.includes(String(pattern || "").toLowerCase()))) return false;
|
||||||
|
if (!rule.when) return true;
|
||||||
|
if ("muted" in rule.when && Boolean(input.muted) !== rule.when.muted) return false;
|
||||||
|
if ("hasReferenceVideo" in rule.when && Boolean(input.hasReferenceVideo) !== rule.when.hasReferenceVideo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEnterpriseVideoCreditRate(
|
||||||
|
input: EnterpriseVideoPricingInput,
|
||||||
|
config: EnterpriseVideoPricingConfig = FALLBACK_ENTERPRISE_VIDEO_PRICING_CONFIG,
|
||||||
|
): number {
|
||||||
const resolution = normalizeEnterpriseResolution(input.resolution);
|
const resolution = normalizeEnterpriseResolution(input.resolution);
|
||||||
const model = String(input.model || "").toLowerCase();
|
const model = String(input.model || "").toLowerCase();
|
||||||
|
const fallbackResolution = normalizeEnterpriseResolution(
|
||||||
|
config.defaultResolution || ENTERPRISE_DEFAULT_VIDEO_RESOLUTION,
|
||||||
|
);
|
||||||
|
const rule = config.rules.find((candidate) => enterpriseVideoPricingRuleMatches(candidate, input, model));
|
||||||
|
|
||||||
if (model.includes("happyhorse")) {
|
if (rule) {
|
||||||
return resolution === "720P" ? 0.72 : 1.28;
|
const rate = rule.rates[resolution] ?? rule.rates[fallbackResolution];
|
||||||
}
|
if (Number.isFinite(rate) && rate >= 0) return rate;
|
||||||
|
|
||||||
if (model.includes("wan2.7-i2v") || model.includes("wanxiang")) {
|
|
||||||
return resolution === "720P" ? 0.6 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.includes("animate-mix")) {
|
|
||||||
return resolution === "720P" ? 0.6 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.includes("s2v")) {
|
|
||||||
return resolution === "720P" ? 0.6 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.includes("vidu")) {
|
|
||||||
return resolution === "720P" ? 0.6 : 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.includes("pixverse")) {
|
|
||||||
return resolution === "720P" ? 0.6 : 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.includes("kling")) {
|
|
||||||
if (input.muted) {
|
|
||||||
if (input.hasReferenceVideo) return resolution === "720P" ? 0.9 : 1.2;
|
|
||||||
return resolution === "720P" ? 0.6 : 0.8;
|
|
||||||
}
|
|
||||||
return resolution === "720P" ? 0.9 : 1.2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unsupported enterprise video model: ${input.model}`);
|
throw new Error(`Unsupported enterprise video model: ${input.model}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateEnterpriseVideoCredits(input: EnterpriseVideoPricingInput): number {
|
export function calculateEnterpriseVideoCredits(
|
||||||
|
input: EnterpriseVideoPricingInput,
|
||||||
|
config: EnterpriseVideoPricingConfig = FALLBACK_ENTERPRISE_VIDEO_PRICING_CONFIG,
|
||||||
|
): number {
|
||||||
const duration = Math.max(1, Math.ceil(Number(input.durationSeconds) || 1));
|
const duration = Math.max(1, Math.ceil(Number(input.durationSeconds) || 1));
|
||||||
return Number((getEnterpriseVideoCreditRate(input) * duration * CREDITS_PER_CNY).toFixed(2));
|
const creditsPerCny = Number(config.creditsPerCny || CREDITS_PER_CNY);
|
||||||
|
return Number((getEnterpriseVideoCreditRate(input, config) * duration * creditsPerCny).toFixed(2));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user