| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822 |
- import { prisma } from '../../models';
- import { MemberLevel } from '../../types';
- // ============================================
- // TTS服务商成本配置(2026-04-12 更新)
- // ============================================
- export const TTS_COST_CONFIG = {
- // TTS服务商配置
- providers: {
- // 免费方案(科大讯飞基础版)
- iflytek_free: {
- name: '科大讯飞-基础版',
- costPerThousand: 0, // 免费
- quality: 'standard',
- voiceOptions: 10,
- freeQuota: 100000, // 10万/月(永久)
- novelRating: 3 // ★★★☆☆
- },
- // 性价比方案(百度基础版)
- baidu_standard: {
- name: '百度智能云-基础版',
- costPerThousand: 3.0, // ¥3/千字符
- quality: 'standard',
- voiceOptions: 20,
- freeQuota: 2000000, // 200万/月(首年)
- novelRating: 4 // ★★★★☆
- },
- // 高质量方案(百度精品版)
- baidu_premium: {
- name: '百度智能云-精品版',
- costPerThousand: 15.0, // ¥15/千字符
- quality: 'premium',
- voiceOptions: 50,
- freeQuota: 0,
- novelRating: 5 // ★★★★★
- },
- // 阿里云方案
- aliyun_plus: {
- name: '阿里云Qwen3-Plus',
- costPerThousand: 12.0, // ¥12/千字符
- quality: 'high',
- voiceOptions: 30,
- freeQuota: 0,
- novelRating: 5 // ★★★★★
- },
- // 企业方案(火山引擎)
- volcano_premium: {
- name: '火山引擎豆包-标准版',
- costPerThousand: 50.0, // ¥50/千字符
- quality: 'premium',
- voiceOptions: 40,
- freeQuota: 20000, // 2万/应用
- novelRating: 5 // ★★★★★
- }
- },
-
- // AI模型成本配置
- aiModels: {
- deepseek: {
- name: 'DeepSeek V3.2',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 8.0, // ¥8.0/千token
- quality: 'standard',
- default: true
- },
- qwen_plus: {
- name: '通义Qwen Plus',
- inputCost: 4.0, // ¥4.0/千token
- outputCost: 12.0, // ¥12.0/千token
- quality: 'high'
- },
- doubao_pro: {
- name: '豆包 Pro',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 6.0, // ¥6.0/千token
- quality: 'high'
- }
- },
-
- // 各等级默认TTS方案
- planTTSProvider: {
- 0: 'iflytek_free', // 免费版:科大讯飞免费
- 1: 'baidu_standard', // 入门版:百度基础
- 2: 'baidu_premium', // 专业版:百度精品
- 3: 'volcano_premium' // 旗舰版:火山引擎
- },
-
- // 默认AI模型
- defaultAIModel: 'deepseek'
- };
- // ============================================
- // Token成本配置(AI生成成本 + TTS成本)
- // ============================================
- export const TOKEN_COST_CONFIG = {
- // 基础Token成本(DeepSeek V3.2 + 百度基础TTS)
- baseCostPerThousand: 5.0, // AI: ¥2 + TTS: ¥3 = ¥5/千字符
-
- // 超额使用单价
- extraPricePerThousand: 6.0, // 略高于成本价
-
- // 模型选择
- models: {
- deepseek: {
- name: 'DeepSeek V3.2',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 8.0, // ¥8.0/千token
- quality: 'standard',
- default: true
- },
- qwen_plus: {
- name: '通义 Qwen Plus',
- inputCost: 4.0, // ¥4.0/千token
- outputCost: 12.0, // ¥12.0/千token
- quality: 'high',
- extraFee: 1.5 // 每千token额外收费
- },
- doubao_pro: {
- name: '豆包 Pro',
- inputCost: 2.0, // ¥2.0/千token
- outputCost: 6.0, // ¥6.0/千token
- quality: 'high'
- }
- }
- };
- // 计算Token生成成本
- export function calculateTokenCost(textLength: number, model: string = 'deepseek'): {
- inputCost: number;
- outputCost: number;
- totalCost: number;
- suggestedPrice: number;
- } {
- const modelConfig = TOKEN_COST_CONFIG.models[model] || TOKEN_COST_CONFIG.models.deepseek;
-
- // 输入成本
- const inputTokens = textLength; // 1字 ≈ 1 token
- const inputCost = (inputTokens / 1000) * modelConfig.inputCost;
-
- // 输出成本(音频合成)
- const outputCost = textLength * 0.001 * modelConfig.outputCost * 0.1;
-
- // 总成本
- const totalCost = inputCost + outputCost;
-
- // 建议售价(成本 × 1.5,保证50%利润)
- const suggestedPrice = totalCost * 1.5;
-
- return {
- inputCost: Math.round(inputCost * 100) / 100,
- outputCost: Math.round(outputCost * 100) / 100,
- totalCost: Math.round(totalCost * 100) / 100,
- suggestedPrice: Math.round(suggestedPrice * 100) / 100
- };
- }
- // 默认套餐配置(包月批发+按量零售,2026-04-12定稿)
- export const DEFAULT_PLANS = [
- {
- name: '免费版',
- level: 0,
- priceMonthly: 0,
- priceYearly: 0,
- description: '适合轻度体验',
- features: JSON.stringify([
- '每天3次生成',
- '每月10分钟音频',
- '每次最多2分钟',
- '基础音色5种',
- '标准音质'
- ]),
- isRecommended: false,
- sortOrder: 0,
- dailyGenerations: 3,
- perGenerationLimit: 300, // ~2分钟音频
- monthlyTokens: 1500,
- monthlyMinutes: 10, // 音频时长配额
- yearlyTokens: null,
- voiceOptions: 5,
- audioQuality: 'standard',
- apiAccess: false,
- batchProcessing: false,
- teamManagement: false,
- overageEnabled: false, // 不支持超出配额
- overagePrice: 0,
- pricing: {
- monthlyPricePerMinute: 5.0, // ¥5/分钟(包月)
- overagePricePerMinute: null // 不支持按量
- }
- },
- {
- name: '入门版',
- level: 1,
- priceMonthly: 19,
- priceYearly: 190,
- description: '适合轻度使用',
- features: JSON.stringify([
- '每天10次生成',
- '每月30分钟音频',
- '每次最多5分钟',
- '全部音色',
- '高清音质',
- '超出¥8/分钟'
- ]),
- isRecommended: false,
- sortOrder: 1,
- dailyGenerations: 10,
- perGenerationLimit: 750, // ~5分钟音频
- monthlyTokens: 4500,
- monthlyMinutes: 30,
- yearlyTokens: null,
- voiceOptions: -1,
- audioQuality: 'high',
- apiAccess: false,
- batchProcessing: false,
- teamManagement: false,
- overageEnabled: true,
- overagePrice: 8.0, // 超出¥8/分钟(零售价)
- pricing: {
- monthlyPricePerMinute: 5.0, // ¥5/分钟(批发)
- overagePricePerMinute: 8.0 // ¥8/分钟(零售)
- }
- },
- {
- name: '专业版',
- level: 2,
- priceMonthly: 59,
- priceYearly: 590,
- description: '适合内容创作者 ⭐推荐',
- features: JSON.stringify([
- '无限制生成',
- '每月100分钟音频',
- '每次最多10分钟',
- '全部音色+定制音色',
- '高清音质',
- '超出¥7/分钟'
- ]),
- isRecommended: true,
- sortOrder: 2,
- dailyGenerations: -1,
- perGenerationLimit: 1500, // ~10分钟音频
- monthlyTokens: 15000,
- monthlyMinutes: 100,
- yearlyTokens: null,
- voiceOptions: -1,
- audioQuality: 'high',
- apiAccess: false,
- batchProcessing: false,
- teamManagement: false,
- overageEnabled: true,
- overagePrice: 7.0, // 超出¥7/分钟
- pricing: {
- monthlyPricePerMinute: 5.0,
- overagePricePerMinute: 7.0
- }
- },
- {
- name: '旗舰版',
- level: 3,
- priceMonthly: 199,
- priceYearly: 1990,
- description: '适合重度使用',
- features: JSON.stringify([
- '无限制生成',
- '每月400分钟音频',
- '每次最多20分钟',
- '全部音色+定制音色',
- '高清音质',
- 'VIP优先队列',
- '超出¥6/分钟'
- ]),
- isRecommended: false,
- sortOrder: 3,
- dailyGenerations: -1,
- perGenerationLimit: 3000, // ~20分钟音频
- monthlyTokens: 60000,
- monthlyMinutes: 400,
- yearlyTokens: null,
- voiceOptions: -1,
- audioQuality: 'high',
- apiAccess: true,
- batchProcessing: false,
- teamManagement: false,
- overageEnabled: true,
- overagePrice: 6.0, // 超出¥6/分钟
- pricing: {
- monthlyPricePerMinute: 5.0,
- overagePricePerMinute: 6.0
- }
- },
- {
- name: '企业版',
- level: 4,
- priceMonthly: 599,
- priceYearly: 5990,
- description: '适合企业用户',
- features: JSON.stringify([
- '无限制生成',
- '每月1500分钟音频',
- '每次最多60分钟',
- '全部功能',
- '高清音质',
- '专属技术支持',
- '批量处理',
- '团队管理',
- '超出¥5/分钟'
- ]),
- isRecommended: false,
- sortOrder: 4,
- dailyGenerations: -1,
- perGenerationLimit: 9000, // ~60分钟音频
- monthlyTokens: 225000,
- monthlyMinutes: 1500,
- yearlyTokens: null,
- voiceOptions: -1,
- audioQuality: 'high',
- apiAccess: true,
- batchProcessing: true,
- teamManagement: true,
- overageEnabled: true,
- overagePrice: 5.0, // 超出¥5/分钟(最低零售价)
- pricing: {
- monthlyPricePerMinute: 5.0,
- overagePricePerMinute: 5.0
- }
- }
- ];
- // 初始化套餐数据
- export async function initializePlans() {
- const existingPlans = await prisma.subscriptionPlan.count();
-
- if (existingPlans === 0) {
- console.log('初始化订阅套餐数据...');
- for (const plan of DEFAULT_PLANS) {
- await prisma.subscriptionPlan.create({ data: plan });
- }
- console.log('订阅套餐初始化完成');
- }
- }
- // 获取所有套餐列表
- export async function getPlans() {
- const plans = await prisma.subscriptionPlan.findMany({
- where: { isActive: true },
- orderBy: { sortOrder: 'asc' }
- });
- return plans.map(plan => ({
- ...plan,
- priceMonthly: Number(plan.priceMonthly),
- priceYearly: Number(plan.priceYearly),
- features: JSON.parse(plan.features || '[]')
- }));
- }
- // 获取单个套餐详情
- export async function getPlanById(planId: number) {
- const plan = await prisma.subscriptionPlan.findUnique({
- where: { id: planId }
- });
- if (!plan) {
- throw new Error('套餐不存在');
- }
- return {
- ...plan,
- priceMonthly: Number(plan.priceMonthly),
- priceYearly: Number(plan.priceYearly),
- features: JSON.parse(plan.features || '[]')
- };
- }
- // 获取用户当前订阅信息
- export async function getUserSubscription(userId: number) {
- const subscription = await prisma.subscription.findFirst({
- where: {
- userId,
- status: 'active',
- endDate: { gt: new Date() }
- },
- include: {
- plan: true
- },
- orderBy: { endDate: 'desc' }
- });
- return subscription;
- }
- // 获取用户Token余额
- export async function getUserTokenBalance(userId: number) {
- let balance = await prisma.tokenBalance.findUnique({
- where: { userId }
- });
- if (!balance) {
- // 初始化Token余额
- balance = await prisma.tokenBalance.create({
- data: {
- userId,
- totalTokens: 10000, // 默认免费额度
- usedTokens: 0
- }
- });
- }
- const remaining = balance.totalTokens - balance.usedTokens;
- return {
- totalTokens: balance.totalTokens,
- usedTokens: balance.usedTokens,
- remainingTokens: remaining,
- resetDate: balance.resetDate,
- isUnlimited: balance.totalTokens === -1
- };
- }
- // 获取Token使用记录
- export async function getTokenUsageList(userId: number, page: number = 1, pageSize: number = 20) {
- const where = { userId };
- const total = await prisma.tokenUsage.count({ where });
-
- const list = await prisma.tokenUsage.findMany({
- where,
- orderBy: { createdAt: 'desc' },
- skip: (page - 1) * pageSize,
- take: pageSize
- });
- return {
- list,
- total,
- page,
- pageSize,
- totalPages: Math.ceil(total / pageSize)
- };
- }
- // 消耗Token
- export async function consumeToken(
- userId: number,
- amount: number,
- type: string,
- contentLength: number,
- description?: string
- ) {
- const balance = await prisma.tokenBalance.findUnique({
- where: { userId }
- });
- if (!balance) {
- throw new Error('用户Token余额不存在');
- }
- if (balance.totalTokens !== -1 && balance.usedTokens + amount > balance.totalTokens) {
- throw new Error('Token余额不足');
- }
- // 更新余额
- await prisma.tokenBalance.update({
- where: { userId },
- data: {
- usedTokens: { increment: amount }
- }
- });
- // 记录使用
- const usage = await prisma.tokenUsage.create({
- data: {
- userId,
- type,
- amount,
- contentLength,
- description
- }
- });
- return usage;
- }
- // 获取用户配额(兼容旧接口)
- export async function getUserQuota(userId: number) {
- const user = await prisma.user.findUnique({
- where: { id: userId }
- });
- if (!user) {
- throw new Error('用户不存在');
- }
- const subscription = await getUserSubscription(userId);
- const tokenBalance = await getUserTokenBalance(userId);
-
- const memberLevel = user.memberLevel as MemberLevel;
- const levelNames = ['免费版', '基础版', '专业版', '旗舰版'];
-
- return {
- level: memberLevel,
- levelName: levelNames[memberLevel] || '免费版',
- isValid: subscription !== null,
- expireAt: subscription?.endDate,
- plan: subscription?.plan ? {
- name: subscription.plan.name,
- features: JSON.parse(subscription.plan.features || '[]')
- } : null,
- tokenBalance,
- limits: subscription?.plan ? {
- dailyGenerations: subscription.plan.dailyGenerations,
- perGenerationLimit: subscription.plan.perGenerationLimit,
- monthlyTokens: subscription.plan.monthlyTokens,
- voiceOptions: subscription.plan.voiceOptions,
- audioQuality: subscription.plan.audioQuality
- } : {
- dailyGenerations: 3,
- perGenerationLimit: 2000,
- monthlyTokens: 10000,
- voiceOptions: 5,
- audioQuality: 'standard'
- }
- };
- }
- // 检查用户配额
- export async function checkQuota(userId: number, requiredTokens: number) {
- const quota = await getUserQuota(userId);
- const balance = quota.tokenBalance;
-
- if (balance.isUnlimited) {
- return { allowed: true, reason: null };
- }
-
- if (balance.remainingTokens < requiredTokens) {
- return {
- allowed: false,
- reason: 'Token余额不足',
- required: requiredTokens,
- remaining: balance.remainingTokens
- };
- }
-
- return { allowed: true, reason: null };
- }
- // ============================================
- // 音频时长计费系统(2026-04-12 定稿)
- // ============================================
- export const AUDIO_BILLING_CONFIG = {
- // 成本配置(综合TTS+AI成本)
- costPerMinute: 4.0, // 成本:¥4/分钟(AI文本生成+TTS合成)
-
- // 定价策略:包月=批发价,按量=零售价
- pricing: {
- // 包月(批发价)
- monthly: {
- basePricePerMinute: 5.0, // ¥5/分钟(30%利润)
- },
- // 按量(零售价)
- payAsYouGo: {
- pricePerMinute: 8.0, // ¥8/分钟(100%利润)
- }
- },
-
- // 语速配置(字/分钟)
- speakingRate: 150,
-
- // 各等级每月音频时长配额(分钟)
- monthlyMinutes: {
- 0: 10, // 免费版:10分钟/月
- 1: 30, // 入门版:30分钟/月
- 2: 100, // 专业版:100分钟/月
- 3: 400, // 旗舰版:400分钟/月
- 4: 1500 // 企业版:1500分钟/月
- },
-
- // 各等级超出配额单价(元/分钟)
- overagePrice: {
- 0: null, // 免费版:不支持超出
- 1: 8.0, // 入门版:¥8/分钟
- 2: 7.0, // 专业版:¥7/分钟
- 3: 6.0, // 旗舰版:¥6/分钟
- 4: 5.0 // 企业版:¥5/分钟
- },
-
- // 各等级单次生成最大时长(分钟)
- maxMinutesPerGeneration: {
- 0: 2, // 免费版:2分钟/次
- 1: 5, // 入门版:5分钟/次
- 2: 10, // 专业版:10分钟/次
- 3: 20, // 旗舰版:20分钟/次
- 4: 60 // 企业版:60分钟/次
- }
- };
- // 根据文本长度计算音频时长
- export function calculateAudioDuration(textLength: number): number {
- return Math.ceil(textLength / AUDIO_BILLING_CONFIG.speakingRate);
- }
- // 计算音频生成费用(支持包月配额和按量计费)
- export function calculateAudioCost(
- textLength: number,
- memberLevel: number = 0,
- usedMinutes: number = 0
- ): {
- textLength: number;
- audioMinutes: number;
- inQuotaMinutes: number;
- overageMinutes: number;
- inQuotaPrice: number; // 配额内价格
- overagePrice: number; // 超额价格
- totalPrice: number;
- estimatedCost: number;
- } {
- const audioMinutes = calculateAudioDuration(textLength);
- const monthlyMinutes = AUDIO_BILLING_CONFIG.monthlyMinutes[memberLevel] || 10;
- const overagePricePerMinute = AUDIO_BILLING_CONFIG.overagePrice[memberLevel];
-
- // 计算配额内和超额分钟数
- const remainingQuota = Math.max(0, monthlyMinutes - usedMinutes);
- const inQuotaMinutes = Math.min(audioMinutes, remainingQuota);
- const overageMinutes = Math.max(0, audioMinutes - inQuotaMinutes);
-
- // 配额内价格(包月批发价)
- const inQuotaPrice = inQuotaMinutes * AUDIO_BILLING_CONFIG.pricing.monthly.basePricePerMinute;
-
- // 超额价格(按量零售价)
- const overagePrice = overageMinutes > 0 && overagePricePerMinute !== null
- ? overageMinutes * overagePricePerMinute
- : 0;
-
- const totalPrice = Math.round((inQuotaPrice + overagePrice) * 100) / 100;
-
- return {
- textLength,
- audioMinutes,
- inQuotaMinutes,
- overageMinutes,
- inQuotaPrice: Math.round(inQuotaPrice * 100) / 100,
- overagePrice: Math.round(overagePrice * 100) / 100,
- totalPrice,
- estimatedCost: audioMinutes * AUDIO_BILLING_CONFIG.costPerMinute
- };
- }
- // 获取用户音频时长余额
- export async function getUserAudioBalance(userId: number) {
- const user = await prisma.user.findUnique({
- where: { id: userId }
- });
-
- if (!user) {
- throw new Error('用户不存在');
- }
-
- const memberLevel = user.memberLevel as MemberLevel;
- const monthlyMinutes = AUDIO_BILLING_CONFIG.monthlyMinutes[memberLevel] || 10;
- const overageEnabled = AUDIO_BILLING_CONFIG.overagePrice[memberLevel] !== null;
- const overagePrice = AUDIO_BILLING_CONFIG.overagePrice[memberLevel];
-
- // 检查是否需要重置月度配额
- const now = new Date();
- const resetDate = user.subscriptionResetDate;
-
- let totalMinutes = monthlyMinutes;
- let usedMinutes = user.usedAudioMinutes || 0;
-
- // 每月1日重置配额
- if (resetDate) {
- const lastReset = new Date(resetDate);
- if (now.getMonth() !== lastReset.getMonth() || now.getFullYear() !== lastReset.getFullYear()) {
- totalMinutes = monthlyMinutes;
- usedMinutes = 0;
- await prisma.user.update({
- where: { id: userId },
- data: {
- usedAudioMinutes: 0,
- subscriptionResetDate: now
- }
- });
- }
- }
-
- return {
- memberLevel,
- totalMinutes,
- usedMinutes,
- remainingMinutes: totalMinutes - usedMinutes,
- overageEnabled,
- overagePrice: overagePrice || 0,
- resetDate: user.subscriptionResetDate
- };
- }
- // 检查音频生成配额
- export async function checkAudioQuota(
- userId: number,
- textLength: number
- ) {
- const audioMinutes = calculateAudioDuration(textLength);
- const balance = await getUserAudioBalance(userId);
- const maxMinutes = AUDIO_BILLING_CONFIG.maxMinutesPerGeneration[balance.memberLevel] || 2;
-
- // 检查单次限制
- if (audioMinutes > maxMinutes) {
- return {
- allowed: false,
- reason: `单次生成不能超过${maxMinutes}分钟`,
- audioMinutes,
- maxMinutes
- };
- }
-
- // 检查月度配额
- if (balance.remainingMinutes < audioMinutes && !balance.overageEnabled) {
- return {
- allowed: false,
- reason: `音频时长不足,本月配额已用完`,
- audioMinutes,
- remainingMinutes: balance.remainingMinutes,
- totalMinutes: balance.totalMinutes,
- overageEnabled: false
- };
- }
-
- return {
- allowed: true,
- reason: null,
- audioMinutes,
- inQuotaMinutes: Math.min(audioMinutes, balance.remainingMinutes),
- overageMinutes: Math.max(0, audioMinutes - balance.remainingMinutes),
- remainingMinutes: balance.remainingMinutes,
- totalMinutes: balance.totalMinutes,
- overageEnabled: balance.overageEnabled,
- overagePrice: balance.overagePrice
- };
- }
- // 扣除音频时长(生成完成后调用)
- export async function consumeAudioMinutes(
- userId: number,
- textLength: number,
- description?: string
- ) {
- const audioMinutes = calculateAudioDuration(textLength);
- const balance = await getUserAudioBalance(userId);
- const cost = calculateAudioCost(textLength, balance.memberLevel, balance.usedMinutes);
-
- // 更新用户已使用时长
- await prisma.user.update({
- where: { id: userId },
- data: {
- usedAudioMinutes: { increment: audioMinutes }
- }
- });
-
- // 记录使用日志
- const usage = await prisma.tokenUsage.create({
- data: {
- userId,
- type: 'audio_generation',
- amount: audioMinutes,
- contentLength: textLength,
- description: description || `${audioMinutes}分钟音频`
- }
- });
-
- return {
- usageId: usage.id,
- audioMinutes,
- textLength,
- inQuotaMinutes: cost.inQuotaMinutes,
- overageMinutes: cost.overageMinutes,
- inQuotaPrice: cost.inQuotaPrice,
- overagePrice: cost.overagePrice,
- totalPrice: cost.totalPrice
- };
- }
- // 获取音频生成预估(用于UI展示)
- export async function getAudioEstimate(
- userId: number,
- textLength: number
- ) {
- const balance = await getUserAudioBalance(userId);
- const cost = calculateAudioCost(textLength, balance.memberLevel, balance.usedMinutes);
- const quota = await checkAudioQuota(userId, textLength);
-
- return {
- // 预估信息
- textLength,
- audioMinutes: cost.audioMinutes,
- inQuotaMinutes: cost.inQuotaMinutes,
- overageMinutes: cost.overageMinutes,
- inQuotaPrice: cost.inQuotaPrice,
- overagePrice: cost.overagePrice,
- estimatedPrice: cost.totalPrice,
- estimatedCost: cost.estimatedCost,
-
- // 用户配额
- remainingMinutes: balance.remainingMinutes,
- totalMinutes: balance.totalMinutes,
- usedMinutes: balance.usedMinutes,
-
- // 计费策略
- overageEnabled: balance.overageEnabled,
- overagePrice: balance.overagePrice,
-
- // 是否允许生成
- canGenerate: quota.allowed,
- reason: quota.reason,
-
- // 显示文本
- displayText: quota.allowed
- ? cost.overageMinutes > 0
- ? `预计${cost.audioMinutes}分钟(配额${cost.inQuotaMinutes}分钟+超出${cost.overageMinutes}分钟),约¥${cost.totalPrice}`
- : `预计${cost.audioMinutes}分钟,约¥${cost.totalPrice}`
- : quota.reason
- };
- }
|