|
|
@ -8,7 +8,6 @@ import '../../core/utils/logger.dart'; |
|
|
class UsageStatsService extends GetxService { |
|
|
class UsageStatsService extends GetxService { |
|
|
static const String _storageKey = 'usage_stats'; |
|
|
static const String _storageKey = 'usage_stats'; |
|
|
static const String _dailyStatsKey = 'daily_translation_stats'; |
|
|
static const String _dailyStatsKey = 'daily_translation_stats'; |
|
|
static const String _costConfigKey = 'translation_cost_config'; |
|
|
|
|
|
|
|
|
|
|
|
final GetStorage _storage = GetStorage(); |
|
|
final GetStorage _storage = GetStorage(); |
|
|
|
|
|
|
|
|
@ -19,13 +18,6 @@ class UsageStatsService extends GetxService { |
|
|
final Rx<TranslationUsageStats> todayStats = |
|
|
final Rx<TranslationUsageStats> todayStats = |
|
|
TranslationUsageStats.empty('').obs; |
|
|
TranslationUsageStats.empty('').obs; |
|
|
|
|
|
|
|
|
// 成本配置 |
|
|
|
|
|
final RxMap<String, double> costConfig = <String, double>{}.obs; |
|
|
|
|
|
|
|
|
|
|
|
// 预警阈值 |
|
|
|
|
|
final RxDouble dailyCostThreshold = 10.0.obs; // 日成本预警阈值 |
|
|
|
|
|
final RxDouble monthlyCostThreshold = 300.0.obs; // 月成本预警阈值 |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
void onInit() { |
|
|
void onInit() { |
|
|
super.onInit(); |
|
|
super.onInit(); |
|
|
@ -34,39 +26,8 @@ class UsageStatsService extends GetxService { |
|
|
|
|
|
|
|
|
/// 初始化服务 |
|
|
/// 初始化服务 |
|
|
void _initializeService() { |
|
|
void _initializeService() { |
|
|
_loadCostConfig(); |
|
|
|
|
|
_loadTodayStats(); |
|
|
_loadTodayStats(); |
|
|
Logger.info('翻译使用量统计服务初始化完成'); |
|
|
Logger.info('lxm--使用量统计服务初始化完成'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 加载成本配置 |
|
|
|
|
|
void _loadCostConfig() { |
|
|
|
|
|
try { |
|
|
|
|
|
final configData = _storage.read(_costConfigKey); |
|
|
|
|
|
if (configData != null) { |
|
|
|
|
|
costConfig.value = Map<String, double>.from(configData); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 设置默认成本配置(每千字符的成本,单位:元) |
|
|
|
|
|
costConfig.value = { |
|
|
|
|
|
'translation_per_1k_chars': 0.05, // 翻译:每千字符0.05元 |
|
|
|
|
|
'asr_per_minute': 0.02, // ASR:每分钟0.02元 |
|
|
|
|
|
'tts_per_1k_chars': 0.03, // TTS:每千字符0.03元 |
|
|
|
|
|
'ai_summary_per_request': 0.01, // AI摘要:每次请求0.01元 |
|
|
|
|
|
}; |
|
|
|
|
|
_saveCostConfig(); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('加载成本配置失败: $e'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 保存成本配置 |
|
|
|
|
|
void _saveCostConfig() { |
|
|
|
|
|
try { |
|
|
|
|
|
_storage.write(_costConfigKey, costConfig.value); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('保存成本配置失败: $e'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 加载当日统计数据 |
|
|
/// 加载当日统计数据 |
|
|
@ -132,14 +93,12 @@ class UsageStatsService extends GetxService { |
|
|
languagePair: languagePair, |
|
|
languagePair: languagePair, |
|
|
translationCount: existing.translationCount + 1, |
|
|
translationCount: existing.translationCount + 1, |
|
|
sourceCharacters: existing.sourceCharacters + sourceText.length, |
|
|
sourceCharacters: existing.sourceCharacters + sourceText.length, |
|
|
cost: existing.cost + _calculateTranslationCost(sourceText.length), |
|
|
|
|
|
); |
|
|
); |
|
|
} else { |
|
|
} else { |
|
|
newLanguagePairStats[languagePair] = LanguagePairStats( |
|
|
newLanguagePairStats[languagePair] = LanguagePairStats( |
|
|
languagePair: languagePair, |
|
|
languagePair: languagePair, |
|
|
translationCount: 1, |
|
|
translationCount: 1, |
|
|
sourceCharacters: sourceText.length, |
|
|
sourceCharacters: sourceText.length, |
|
|
cost: _calculateTranslationCost(sourceText.length), |
|
|
|
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -152,14 +111,12 @@ class UsageStatsService extends GetxService { |
|
|
apiCalls: existing.apiCalls + 1, |
|
|
apiCalls: existing.apiCalls + 1, |
|
|
sourceCharacters: existing.sourceCharacters + sourceText.length, |
|
|
sourceCharacters: existing.sourceCharacters + sourceText.length, |
|
|
ttsCharacters: existing.ttsCharacters + targetText.length, |
|
|
ttsCharacters: existing.ttsCharacters + targetText.length, |
|
|
cost: existing.cost + _calculateTranslationCost(sourceText.length), |
|
|
|
|
|
); |
|
|
); |
|
|
} else { |
|
|
} else { |
|
|
newModeStats[mode] = TranslationModeStats( |
|
|
newModeStats[mode] = TranslationModeStats( |
|
|
apiCalls: 1, |
|
|
apiCalls: 1, |
|
|
sourceCharacters: sourceText.length, |
|
|
sourceCharacters: sourceText.length, |
|
|
ttsCharacters: targetText.length, |
|
|
ttsCharacters: targetText.length, |
|
|
cost: _calculateTranslationCost(sourceText.length), |
|
|
|
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -171,17 +128,9 @@ class UsageStatsService extends GetxService { |
|
|
todayStats.value.ttsTextCharacters + targetText.length, |
|
|
todayStats.value.ttsTextCharacters + targetText.length, |
|
|
languagePairStats: newLanguagePairStats, |
|
|
languagePairStats: newLanguagePairStats, |
|
|
modeStats: newModeStats, |
|
|
modeStats: newModeStats, |
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( |
|
|
|
|
|
translationApiCalls: todayStats.value.translationApiCalls + 1, |
|
|
|
|
|
sourceTextCharacters: |
|
|
|
|
|
todayStats.value.sourceTextCharacters + sourceText.length, |
|
|
|
|
|
ttsTextCharacters: |
|
|
|
|
|
todayStats.value.ttsTextCharacters + targetText.length, |
|
|
|
|
|
)), |
|
|
|
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
_saveTodayStats(); |
|
|
_checkCostThreshold(); |
|
|
|
|
|
|
|
|
|
|
|
Logger.info( |
|
|
Logger.info( |
|
|
'记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符'); |
|
|
'记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符'); |
|
|
@ -190,7 +139,7 @@ class UsageStatsService extends GetxService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 记录ASR API调用 - 优化版(精确计费参数) |
|
|
/// 记录ASR API调用 - 优化版(精确统计参数) |
|
|
void recordAsrApiCall({ |
|
|
void recordAsrApiCall({ |
|
|
required int effectiveDurationSeconds, // 有效语音时长(VAD检测的实际语音) |
|
|
required int effectiveDurationSeconds, // 有效语音时长(VAD检测的实际语音) |
|
|
required int totalDurationSeconds, // 总会话时长(包含静音) |
|
|
required int totalDurationSeconds, // 总会话时长(包含静音) |
|
|
@ -213,12 +162,10 @@ class UsageStatsService extends GetxService { |
|
|
newModeStats[mode] = existing.copyWith( |
|
|
newModeStats[mode] = existing.copyWith( |
|
|
effectiveAudioDuration: |
|
|
effectiveAudioDuration: |
|
|
existing.effectiveAudioDuration + effectiveDurationSeconds, |
|
|
existing.effectiveAudioDuration + effectiveDurationSeconds, |
|
|
cost: existing.cost + _calculateAsrCost(effectiveDurationSeconds), |
|
|
|
|
|
); |
|
|
); |
|
|
} else { |
|
|
} else { |
|
|
newModeStats[mode] = TranslationModeStats( |
|
|
newModeStats[mode] = TranslationModeStats( |
|
|
effectiveAudioDuration: effectiveDurationSeconds, |
|
|
effectiveAudioDuration: effectiveDurationSeconds, |
|
|
cost: _calculateAsrCost(effectiveDurationSeconds), |
|
|
|
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -230,11 +177,6 @@ class UsageStatsService extends GetxService { |
|
|
totalSessionDuration: |
|
|
totalSessionDuration: |
|
|
todayStats.value.totalSessionDuration + totalDurationSeconds, |
|
|
todayStats.value.totalSessionDuration + totalDurationSeconds, |
|
|
modeStats: newModeStats, |
|
|
modeStats: newModeStats, |
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( |
|
|
|
|
|
asrApiCalls: todayStats.value.asrApiCalls + 1, |
|
|
|
|
|
effectiveAudioDuration: todayStats.value.effectiveAudioDuration + |
|
|
|
|
|
effectiveDurationSeconds, |
|
|
|
|
|
)), |
|
|
|
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
_saveTodayStats(); |
|
|
@ -263,10 +205,6 @@ class UsageStatsService extends GetxService { |
|
|
ttsTextCharacters: todayStats.value.ttsTextCharacters + text.length, |
|
|
ttsTextCharacters: todayStats.value.ttsTextCharacters + text.length, |
|
|
totalSessionDuration: |
|
|
totalSessionDuration: |
|
|
todayStats.value.totalSessionDuration + durationSeconds, |
|
|
todayStats.value.totalSessionDuration + durationSeconds, |
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( |
|
|
|
|
|
ttsApiCalls: todayStats.value.ttsApiCalls + 1, |
|
|
|
|
|
ttsTextCharacters: todayStats.value.ttsTextCharacters + text.length, |
|
|
|
|
|
)), |
|
|
|
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
_saveTodayStats(); |
|
|
@ -283,9 +221,6 @@ class UsageStatsService extends GetxService { |
|
|
// 更新当日统计 |
|
|
// 更新当日统计 |
|
|
todayStats.value = todayStats.value.copyWith( |
|
|
todayStats.value = todayStats.value.copyWith( |
|
|
aiSummaryApiCalls: todayStats.value.aiSummaryApiCalls + 1, |
|
|
aiSummaryApiCalls: todayStats.value.aiSummaryApiCalls + 1, |
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( |
|
|
|
|
|
aiSummaryApiCalls: todayStats.value.aiSummaryApiCalls + 1, |
|
|
|
|
|
)), |
|
|
|
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
_saveTodayStats(); |
|
|
@ -309,45 +244,6 @@ class UsageStatsService extends GetxService { |
|
|
Logger.info('开始新的翻译会话'); |
|
|
Logger.info('开始新的翻译会话'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 计算预估成本 - 优化版(基于精确计费参数) |
|
|
|
|
|
double _calculateEstimatedCost(TranslationUsageStats stats) { |
|
|
|
|
|
double cost = 0.0; |
|
|
|
|
|
|
|
|
|
|
|
// 翻译成本:基于源文本字符数 |
|
|
|
|
|
cost += (stats.sourceTextCharacters / 1000) * |
|
|
|
|
|
(costConfig['translation_per_1k_chars'] ?? 0.05); |
|
|
|
|
|
|
|
|
|
|
|
// ASR成本:基于有效音频时长 |
|
|
|
|
|
cost += (stats.effectiveAudioDuration / 60) * |
|
|
|
|
|
(costConfig['asr_per_minute'] ?? 0.02); |
|
|
|
|
|
|
|
|
|
|
|
// TTS成本:基于TTS文本字符数 |
|
|
|
|
|
cost += (stats.ttsTextCharacters / 1000) * |
|
|
|
|
|
(costConfig['tts_per_1k_chars'] ?? 0.03); |
|
|
|
|
|
|
|
|
|
|
|
// AI摘要成本(已废弃但保留兼容性) |
|
|
|
|
|
cost += stats.aiSummaryApiCalls * |
|
|
|
|
|
(costConfig['ai_summary_per_request'] ?? 0.01); |
|
|
|
|
|
|
|
|
|
|
|
return cost; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 计算翻译成本(单次调用) |
|
|
|
|
|
double _calculateTranslationCost(int sourceCharacters) { |
|
|
|
|
|
return (sourceCharacters / 1000) * |
|
|
|
|
|
(costConfig['translation_per_1k_chars'] ?? 0.05); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 计算ASR成本(单次调用) |
|
|
|
|
|
double _calculateAsrCost(int durationSeconds) { |
|
|
|
|
|
return (durationSeconds / 60) * (costConfig['asr_per_minute'] ?? 0.02); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 计算TTS成本(单次调用) |
|
|
|
|
|
double _calculateTtsCost(int textCharacters) { |
|
|
|
|
|
return (textCharacters / 1000) * (costConfig['tts_per_1k_chars'] ?? 0.03); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 保存当日统计数据 |
|
|
/// 保存当日统计数据 |
|
|
void _saveTodayStats() { |
|
|
void _saveTodayStats() { |
|
|
try { |
|
|
try { |
|
|
@ -360,27 +256,6 @@ class UsageStatsService extends GetxService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 检查成本阈值 |
|
|
|
|
|
void _checkCostThreshold() { |
|
|
|
|
|
final todayCost = todayStats.value.estimatedCost; |
|
|
|
|
|
|
|
|
|
|
|
if (todayCost >= dailyCostThreshold.value) { |
|
|
|
|
|
_showCostWarning('今日翻译成本已达到预警阈值:¥${todayCost.toStringAsFixed(2)}'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 显示成本预警 |
|
|
|
|
|
void _showCostWarning(String message) { |
|
|
|
|
|
Get.snackbar( |
|
|
|
|
|
'成本预警', |
|
|
|
|
|
message, |
|
|
|
|
|
snackPosition: SnackPosition.TOP, |
|
|
|
|
|
backgroundColor: Get.theme.colorScheme.error.withOpacity(0.1), |
|
|
|
|
|
colorText: Get.theme.colorScheme.error, |
|
|
|
|
|
duration: const Duration(seconds: 5), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 获取历史统计数据 |
|
|
/// 获取历史统计数据 |
|
|
Future<List<TranslationUsageStats>> getHistoryStats({ |
|
|
Future<List<TranslationUsageStats>> getHistoryStats({ |
|
|
required DateTime startDate, |
|
|
required DateTime startDate, |
|
|
@ -434,39 +309,6 @@ class UsageStatsService extends GetxService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 更新成本配置 |
|
|
|
|
|
void updateCostConfig(Map<String, double> newConfig) { |
|
|
|
|
|
costConfig.value = newConfig; |
|
|
|
|
|
_saveCostConfig(); |
|
|
|
|
|
|
|
|
|
|
|
// 重新计算当日成本 |
|
|
|
|
|
todayStats.value = todayStats.value.copyWith( |
|
|
|
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value), |
|
|
|
|
|
); |
|
|
|
|
|
_saveTodayStats(); |
|
|
|
|
|
|
|
|
|
|
|
Logger.info('更新成本配置完成'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 更新预警阈值 |
|
|
|
|
|
void updateThresholds({ |
|
|
|
|
|
double? dailyThreshold, |
|
|
|
|
|
double? monthlyThreshold, |
|
|
|
|
|
}) { |
|
|
|
|
|
if (dailyThreshold != null) { |
|
|
|
|
|
dailyCostThreshold.value = dailyThreshold; |
|
|
|
|
|
} |
|
|
|
|
|
if (monthlyThreshold != null) { |
|
|
|
|
|
monthlyCostThreshold.value = monthlyThreshold; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保存阈值配置 |
|
|
|
|
|
_storage.write('daily_cost_threshold', dailyCostThreshold.value); |
|
|
|
|
|
_storage.write('monthly_cost_threshold', monthlyCostThreshold.value); |
|
|
|
|
|
|
|
|
|
|
|
Logger.info('更新预警阈值完成'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 获取月度统计汇总 |
|
|
/// 获取月度统计汇总 |
|
|
Future<TranslationUsageStats> getMonthlyStats(int year, int month) async { |
|
|
Future<TranslationUsageStats> getMonthlyStats(int year, int month) async { |
|
|
try { |
|
|
try { |
|
|
@ -492,7 +334,7 @@ class UsageStatsService extends GetxService { |
|
|
int totalRecordingDuration = 0; |
|
|
int totalRecordingDuration = 0; |
|
|
int totalTtsPlayDuration = 0; |
|
|
int totalTtsPlayDuration = 0; |
|
|
int totalSessions = 0; |
|
|
int totalSessions = 0; |
|
|
double totalCost = 0.0; |
|
|
//double totalCost = 0.0; |
|
|
|
|
|
|
|
|
final Map<String, int> combinedLanguagePairUsage = {}; |
|
|
final Map<String, int> combinedLanguagePairUsage = {}; |
|
|
final Map<String, int> combinedModeUsage = {}; |
|
|
final Map<String, int> combinedModeUsage = {}; |
|
|
@ -507,7 +349,7 @@ class UsageStatsService extends GetxService { |
|
|
totalRecordingDuration += stats.audioRecordingDuration; |
|
|
totalRecordingDuration += stats.audioRecordingDuration; |
|
|
totalTtsPlayDuration += stats.ttsPlaybackDuration; |
|
|
totalTtsPlayDuration += stats.ttsPlaybackDuration; |
|
|
totalSessions += stats.sessionCount; |
|
|
totalSessions += stats.sessionCount; |
|
|
totalCost += stats.estimatedCost; |
|
|
// totalCost += stats.estimatedCost; |
|
|
|
|
|
|
|
|
// 合并语言对使用统计 |
|
|
// 合并语言对使用统计 |
|
|
stats.languagePairUsage.forEach((key, value) { |
|
|
stats.languagePairUsage.forEach((key, value) { |
|
|
@ -535,7 +377,7 @@ class UsageStatsService extends GetxService { |
|
|
ttsTextCharacters: totalTargetChars, |
|
|
ttsTextCharacters: totalTargetChars, |
|
|
totalSessionDuration: totalTtsPlayDuration, |
|
|
totalSessionDuration: totalTtsPlayDuration, |
|
|
sessionCount: totalSessions, |
|
|
sessionCount: totalSessions, |
|
|
estimatedCost: totalCost, |
|
|
// estimatedCost: totalCost, |
|
|
); |
|
|
); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('获取月度统计失败: $e'); |
|
|
Logger.error('获取月度统计失败: $e'); |
|
|
|