feat: add local auth billing and usage management
This commit is contained in:
1 parent
f642b5e71f
commit
196fdde83f
119 files changed
+15695
-2650
No files matched your search
@@ -0,0 +1,289 @@
|
||||
import {
|
||||
createBillingPriceRule,
|
||||
listBillingPriceRules,
|
||||
updateBillingPriceRule,
|
||||
type BillingPriceRuleInput
|
||||
} from "@/lib/server/billing-store";
|
||||
import type { BillingPriceRule } from "@/lib/types";
|
||||
|
||||
export const BILLING_USD_CNY_RATE = 7.2;
|
||||
export const DEFAULT_BILLING_MARKUP_MULTIPLIER = 1.2;
|
||||
export const BILLING_CATALOG_OBSERVED_AT = "2026-08-11";
|
||||
|
||||
const BAILIAN_IMAGE_URL = "https://help.aliyun.com/zh/model-studio/wan2-7-image-pro";
|
||||
const BAILIAN_VIDEO_URL = "https://help.aliyun.com/zh/model-studio/wan2-7-i2v";
|
||||
const EVOLINK_IMAGE_URL = "https://evolink.ai/zh/gpt-image-2";
|
||||
const VOLC_ARK_PRICING_URL = "https://www.volcengine.com/docs/82379/1544106?lang=zh";
|
||||
const JIMENG_PRICING_URL = "https://www.volcengine.com/activity/jimeng";
|
||||
|
||||
const EVOLINK_GPT_IMAGE_DIMENSIONS = [
|
||||
{
|
||||
key: "quality",
|
||||
label: "生成质量",
|
||||
baselineValue: "medium",
|
||||
defaultValue: "medium",
|
||||
tiers: [
|
||||
{ value: "low", label: "快速", standardFactor: 0.11, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "官方口径约为 medium 的 0.11 倍成本。" },
|
||||
{ value: "medium", label: "标准", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||||
{ value: "high", label: "精细", standardFactor: 4, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "官方口径约为 medium 的 4 倍成本。" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "resolution",
|
||||
label: "分辨率",
|
||||
baselineValue: "1K",
|
||||
defaultValue: "1K",
|
||||
tiers: [
|
||||
{ value: "1K", label: "1K", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||||
{ value: "2K", label: "2K", standardFactor: 4, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "按像素预算相对 1K 的估算倍率。" },
|
||||
{ value: "4K", label: "4K", standardFactor: 8, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "按平台支持的最大像素预算估算。" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "aspectRatio",
|
||||
label: "画面比例",
|
||||
baselineValue: "1:1",
|
||||
defaultValue: "1:1",
|
||||
tiers: [
|
||||
{ value: "1:1", label: "1:1", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||||
{ value: "4:3", label: "4:3", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||||
{ value: "16:9", label: "16:9", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||||
{ value: "9:16", label: "9:16", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||||
{ value: "other", label: "其他比例", match: {}, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "同一分辨率档位下按相同比例预算估算。" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "referenceImageCount",
|
||||
label: "参考图数量",
|
||||
baselineValue: 0,
|
||||
defaultValue: 0,
|
||||
tiers: [
|
||||
{ value: 0, label: "无参考图", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||||
{ value: "1–4", label: "1–4 张", match: { min: 1, max: 4 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" },
|
||||
{ value: "5–8", label: "5–8 张", match: { min: 5, max: 8 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" },
|
||||
{ value: "9–16", label: "9–16 张", match: { min: 9, max: 16 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" }
|
||||
]
|
||||
}
|
||||
] satisfies NonNullable<BillingPriceRuleInput["parameterDimensions"]>;
|
||||
|
||||
export const DEFAULT_BILLING_PRICE_RULES: BillingPriceRuleInput[] = [
|
||||
{
|
||||
id: "base-volcengine-jimeng-seedream46",
|
||||
provider: "volcengine-visual",
|
||||
capability: "image.generate",
|
||||
reqKey: "jimeng_seedream46_cvtob",
|
||||
unit: "image",
|
||||
standardUnitPriceFen: 20,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "即梦4.6参考基准:公开活动页按 ¥200/1000 张折算;官方实时价格以供应商控制台为准。",
|
||||
source: {
|
||||
url: JIMENG_PRICING_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 0.2,
|
||||
basis: "公开资源包 ¥200/1000 张折算;非 API 实时刊例价",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-evolink-gpt-image-2",
|
||||
provider: "evolink",
|
||||
capability: "image.generate",
|
||||
reqKey: "gpt-image-2",
|
||||
unit: "image",
|
||||
standardUnitPriceFen: 34,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "EvoLink 按 token 计费;当前以 medium / 1K / 1:1 / 无参考图作为平台标准基准,参数档位另列。",
|
||||
parameterDimensions: EVOLINK_GPT_IMAGE_DIMENSIONS,
|
||||
source: {
|
||||
url: EVOLINK_IMAGE_URL,
|
||||
currency: "USD",
|
||||
unitPrice: 0.047,
|
||||
fxRate: BILLING_USD_CNY_RATE,
|
||||
basis: "页面估算:medium、1K、1:1、无参考图,约 $0.047/张;折算 ¥0.3384,账本按 ¥0.34",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-bailian-wan27-image-pro",
|
||||
provider: "bailian",
|
||||
capability: "image.generate",
|
||||
reqKey: "wan2.7-image-pro",
|
||||
unit: "image",
|
||||
standardUnitPriceFen: 50,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "北京地域官方基础价:¥0.50/张。",
|
||||
source: {
|
||||
url: BAILIAN_IMAGE_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 0.5,
|
||||
basis: "北京地域,按成功生成图片计费",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-bailian-wan27-i2v-720p",
|
||||
provider: "bailian",
|
||||
capability: "video.generate",
|
||||
reqKey: "wan2.7-i2v-2026-04-25",
|
||||
variantKey: "resolution=720p",
|
||||
unit: "video_second",
|
||||
standardUnitPriceFen: 60,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "北京地域官方基础价:720P ¥0.60/秒。",
|
||||
source: {
|
||||
url: BAILIAN_VIDEO_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 0.6,
|
||||
basis: "北京地域,720P,按成功视频秒数计费",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-bailian-wan27-i2v-1080p",
|
||||
provider: "bailian",
|
||||
capability: "video.generate",
|
||||
reqKey: "wan2.7-i2v-2026-04-25",
|
||||
variantKey: "resolution=1080p",
|
||||
unit: "video_second",
|
||||
standardUnitPriceFen: 100,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "北京地域官方基础价:1080P ¥1.00/秒。",
|
||||
source: {
|
||||
url: BAILIAN_VIDEO_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 1,
|
||||
basis: "北京地域,1080P,按成功视频秒数计费",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-seedance-2-0-480p",
|
||||
provider: "seedance",
|
||||
capability: "video.generate",
|
||||
reqKey: "doubao-seedance-2-0-260128",
|
||||
variantKey: "resolution=480p",
|
||||
unit: "video_second",
|
||||
standardUnitPriceFen: 46,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "官方 5 秒、16:9、无输入视频样例折算:¥2.31/5秒≈¥0.46/秒;实际按方舟 token 用量结算。",
|
||||
source: {
|
||||
url: VOLC_ARK_PRICING_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 0.46,
|
||||
basis: "doubao-seedance-2.0,480P,无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-seedance-2-0-720p",
|
||||
provider: "seedance",
|
||||
capability: "video.generate",
|
||||
reqKey: "doubao-seedance-2-0-260128",
|
||||
variantKey: "resolution=720p",
|
||||
unit: "video_second",
|
||||
standardUnitPriceFen: 99,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "官方 5 秒、16:9、无输入视频样例折算:¥4.97/5秒≈¥0.99/秒;实际按方舟 token 用量结算。",
|
||||
source: {
|
||||
url: VOLC_ARK_PRICING_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 0.99,
|
||||
basis: "doubao-seedance-2.0,720P,无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-seedance-2-0-1080p",
|
||||
provider: "seedance",
|
||||
capability: "video.generate",
|
||||
reqKey: "doubao-seedance-2-0-260128",
|
||||
variantKey: "resolution=1080p",
|
||||
unit: "video_second",
|
||||
standardUnitPriceFen: 248,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "官方 5 秒、16:9、无输入视频样例折算:¥12.39/5秒≈¥2.48/秒;实际按方舟 token 用量结算。",
|
||||
source: {
|
||||
url: VOLC_ARK_PRICING_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 2.48,
|
||||
basis: "doubao-seedance-2.0,1080P,无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "base-seedance-2-0-4k",
|
||||
provider: "seedance",
|
||||
capability: "video.generate",
|
||||
reqKey: "doubao-seedance-2-0-260128",
|
||||
variantKey: "resolution=4k",
|
||||
unit: "video_second",
|
||||
standardUnitPriceFen: 505,
|
||||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||||
enabled: true,
|
||||
note: "官方 5 秒、16:9、无输入视频样例折算:¥25.27/5秒≈¥5.05/秒;实际按方舟 token 用量结算。",
|
||||
source: {
|
||||
url: VOLC_ARK_PRICING_URL,
|
||||
currency: "CNY",
|
||||
unitPrice: 5.05,
|
||||
basis: "doubao-seedance-2.0,4K,无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
|
||||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export async function ensureDefaultBillingPriceRules(): Promise<BillingPriceRule[]> {
|
||||
const existing = await listBillingPriceRules({ includeDisabled: true });
|
||||
const created: BillingPriceRule[] = [];
|
||||
for (const candidate of DEFAULT_BILLING_PRICE_RULES) {
|
||||
const candidateKey = priceRuleMatchKey(candidate);
|
||||
const existingRule = existing.find((rule) => priceRuleMatchKey(rule) === candidateKey);
|
||||
if (existingRule) {
|
||||
const updated = await updateBillingPriceRule(existingRule.id, {
|
||||
reqKey: candidate.reqKey,
|
||||
variantKey: candidate.variantKey,
|
||||
unit: candidate.unit,
|
||||
standardUnitPriceFen: candidate.standardUnitPriceFen,
|
||||
enabled: candidate.enabled,
|
||||
conditions: candidate.conditions,
|
||||
quantitySource: candidate.quantitySource,
|
||||
priority: candidate.priority,
|
||||
note: candidate.note,
|
||||
source: candidate.source,
|
||||
parameterDimensions: candidate.parameterDimensions
|
||||
});
|
||||
if (updated) {
|
||||
const index = existing.findIndex((rule) => rule.id === updated.id);
|
||||
if (index >= 0) existing[index] = updated;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (existing.some((rule) => rule.provider === candidate.provider
|
||||
&& rule.capability === candidate.capability
|
||||
&& !rule.reqKey
|
||||
&& !rule.variantKey)) continue;
|
||||
try {
|
||||
const rule = await createBillingPriceRule(candidate);
|
||||
created.push(rule);
|
||||
} catch (error) {
|
||||
if (!isConflict(error)) throw error;
|
||||
}
|
||||
}
|
||||
return [...existing, ...created];
|
||||
}
|
||||
|
||||
function priceRuleMatchKey(rule: Pick<BillingPriceRule, "provider" | "capability" | "reqKey" | "variantKey" | "conditions">): string {
|
||||
const conditions = Object.fromEntries(Object.entries(rule.conditions || {}).sort(([left], [right]) => left.localeCompare(right)));
|
||||
return [rule.provider, rule.capability, rule.reqKey || "", rule.variantKey || "", JSON.stringify(conditions)].join("\u0000");
|
||||
}
|
||||
|
||||
function isConflict(error: unknown): boolean {
|
||||
return typeof error === "object" && error !== null && "status" in error && (error as { status?: unknown }).status === 409;
|
||||
}
|
||||
Reference in new issue
Block a user