|
|
|
@ -9,22 +9,23 @@ class TranslationUsageService extends GetxService { |
|
|
|
static const String _storageKey = 'translation_usage_stats'; |
|
|
|
static const String _dailyStatsKey = 'daily_translation_stats'; |
|
|
|
static const String _costConfigKey = 'translation_cost_config'; |
|
|
|
|
|
|
|
|
|
|
|
final GetStorage _storage = GetStorage(); |
|
|
|
|
|
|
|
|
|
|
|
// 实时统计数据 |
|
|
|
final Rx<RealtimeUsageStats> realtimeStats = RealtimeUsageStats().obs; |
|
|
|
|
|
|
|
|
|
|
|
// 当日统计数据 |
|
|
|
final Rx<TranslationUsageStats> todayStats = TranslationUsageStats.empty('').obs; |
|
|
|
|
|
|
|
final Rx<TranslationUsageStats> todayStats = |
|
|
|
TranslationUsageStats.empty('').obs; |
|
|
|
|
|
|
|
// 成本配置 |
|
|
|
final RxMap<String, double> costConfig = <String, double>{}.obs; |
|
|
|
|
|
|
|
|
|
|
|
// 预警阈值 |
|
|
|
final RxDouble dailyCostThreshold = 10.0.obs; // 日成本预警阈值 |
|
|
|
final RxDouble dailyCostThreshold = 50.0.obs; // 日成本预警阈值 |
|
|
|
final RxDouble monthlyCostThreshold = 300.0.obs; // 月成本预警阈值 |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
void onInit() { |
|
|
|
super.onInit(); |
|
|
|
@ -72,11 +73,13 @@ class TranslationUsageService extends GetxService { |
|
|
|
void _loadTodayStats() { |
|
|
|
try { |
|
|
|
final today = DateTime.now(); |
|
|
|
final todayKey = '${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; |
|
|
|
|
|
|
|
final todayKey = |
|
|
|
'${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; |
|
|
|
|
|
|
|
final statsData = _storage.read('${_dailyStatsKey}_$todayKey'); |
|
|
|
if (statsData != null) { |
|
|
|
todayStats.value = TranslationUsageStats.fromJson(Map<String, dynamic>.from(statsData)); |
|
|
|
todayStats.value = TranslationUsageStats.fromJson( |
|
|
|
Map<String, dynamic>.from(statsData)); |
|
|
|
} else { |
|
|
|
todayStats.value = TranslationUsageStats.empty(getCurrentUserId()); |
|
|
|
} |
|
|
|
@ -112,7 +115,8 @@ class TranslationUsageService extends GetxService { |
|
|
|
realtimeStats.value.translationCount++; |
|
|
|
realtimeStats.value.sourceTextCharacters += sourceText.length; |
|
|
|
realtimeStats.value.ttsTextCharacters += targetText.length; |
|
|
|
realtimeStats.value.currentLanguagePair = '$sourceLanguage-$targetLanguage'; |
|
|
|
realtimeStats.value.currentLanguagePair = |
|
|
|
'$sourceLanguage-$targetLanguage'; |
|
|
|
realtimeStats.value.currentMode = mode; |
|
|
|
realtimeStats.refresh(); |
|
|
|
|
|
|
|
@ -120,7 +124,8 @@ class TranslationUsageService extends GetxService { |
|
|
|
final languagePair = '$sourceLanguage→$targetLanguage'; |
|
|
|
|
|
|
|
// 更新语言对统计 |
|
|
|
final newLanguagePairStats = Map<String, LanguagePairStats>.from(todayStats.value.languagePairStats); |
|
|
|
final newLanguagePairStats = Map<String, LanguagePairStats>.from( |
|
|
|
todayStats.value.languagePairStats); |
|
|
|
if (newLanguagePairStats.containsKey(languagePair)) { |
|
|
|
final existing = newLanguagePairStats[languagePair]!; |
|
|
|
newLanguagePairStats[languagePair] = LanguagePairStats( |
|
|
|
@ -139,7 +144,8 @@ class TranslationUsageService extends GetxService { |
|
|
|
} |
|
|
|
|
|
|
|
// 更新模式统计 |
|
|
|
final newModeStats = Map<String, TranslationModeStats>.from(todayStats.value.modeStats); |
|
|
|
final newModeStats = |
|
|
|
Map<String, TranslationModeStats>.from(todayStats.value.modeStats); |
|
|
|
if (newModeStats.containsKey(mode)) { |
|
|
|
final existing = newModeStats[mode]!; |
|
|
|
newModeStats[mode] = existing.copyWith( |
|
|
|
@ -159,21 +165,26 @@ class TranslationUsageService extends GetxService { |
|
|
|
|
|
|
|
todayStats.value = todayStats.value.copyWith( |
|
|
|
translationApiCalls: todayStats.value.translationApiCalls + 1, |
|
|
|
sourceTextCharacters: todayStats.value.sourceTextCharacters + sourceText.length, |
|
|
|
ttsTextCharacters: todayStats.value.ttsTextCharacters + targetText.length, |
|
|
|
sourceTextCharacters: |
|
|
|
todayStats.value.sourceTextCharacters + sourceText.length, |
|
|
|
ttsTextCharacters: |
|
|
|
todayStats.value.ttsTextCharacters + targetText.length, |
|
|
|
languagePairStats: newLanguagePairStats, |
|
|
|
modeStats: newModeStats, |
|
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( |
|
|
|
translationApiCalls: todayStats.value.translationApiCalls + 1, |
|
|
|
sourceTextCharacters: todayStats.value.sourceTextCharacters + sourceText.length, |
|
|
|
ttsTextCharacters: todayStats.value.ttsTextCharacters + targetText.length, |
|
|
|
sourceTextCharacters: |
|
|
|
todayStats.value.sourceTextCharacters + sourceText.length, |
|
|
|
ttsTextCharacters: |
|
|
|
todayStats.value.ttsTextCharacters + targetText.length, |
|
|
|
)), |
|
|
|
); |
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
|
_checkCostThreshold(); |
|
|
|
|
|
|
|
Logger.info('记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符'); |
|
|
|
|
|
|
|
Logger.info( |
|
|
|
'记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符'); |
|
|
|
} catch (e) { |
|
|
|
Logger.error('记录翻译API调用失败: $e'); |
|
|
|
} |
|
|
|
@ -195,11 +206,13 @@ class TranslationUsageService extends GetxService { |
|
|
|
realtimeStats.refresh(); |
|
|
|
|
|
|
|
// 更新模式统计 |
|
|
|
final newModeStats = Map<String, TranslationModeStats>.from(todayStats.value.modeStats); |
|
|
|
final newModeStats = |
|
|
|
Map<String, TranslationModeStats>.from(todayStats.value.modeStats); |
|
|
|
if (newModeStats.containsKey(mode)) { |
|
|
|
final existing = newModeStats[mode]!; |
|
|
|
newModeStats[mode] = existing.copyWith( |
|
|
|
effectiveAudioDuration: existing.effectiveAudioDuration + effectiveDurationSeconds, |
|
|
|
effectiveAudioDuration: |
|
|
|
existing.effectiveAudioDuration + effectiveDurationSeconds, |
|
|
|
cost: existing.cost + _calculateAsrCost(effectiveDurationSeconds), |
|
|
|
); |
|
|
|
} else { |
|
|
|
@ -212,18 +225,22 @@ class TranslationUsageService extends GetxService { |
|
|
|
// 更新当日统计 |
|
|
|
todayStats.value = todayStats.value.copyWith( |
|
|
|
asrApiCalls: todayStats.value.asrApiCalls + 1, |
|
|
|
effectiveAudioDuration: todayStats.value.effectiveAudioDuration + effectiveDurationSeconds, |
|
|
|
totalSessionDuration: todayStats.value.totalSessionDuration + totalDurationSeconds, |
|
|
|
effectiveAudioDuration: |
|
|
|
todayStats.value.effectiveAudioDuration + effectiveDurationSeconds, |
|
|
|
totalSessionDuration: |
|
|
|
todayStats.value.totalSessionDuration + totalDurationSeconds, |
|
|
|
modeStats: newModeStats, |
|
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( |
|
|
|
asrApiCalls: todayStats.value.asrApiCalls + 1, |
|
|
|
effectiveAudioDuration: todayStats.value.effectiveAudioDuration + effectiveDurationSeconds, |
|
|
|
effectiveAudioDuration: todayStats.value.effectiveAudioDuration + |
|
|
|
effectiveDurationSeconds, |
|
|
|
)), |
|
|
|
); |
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
|
|
|
|
|
Logger.info('ASR统计: 有效语音${effectiveDurationSeconds}秒, 总时长${totalDurationSeconds}秒, 语言$language, 模式$mode'); |
|
|
|
Logger.info( |
|
|
|
'ASR统计: 有效语音${effectiveDurationSeconds}秒, 总时长${totalDurationSeconds}秒, 语言$language, 模式$mode'); |
|
|
|
} catch (e) { |
|
|
|
Logger.error('记录ASR API调用失败: $e'); |
|
|
|
} |
|
|
|
@ -244,7 +261,8 @@ class TranslationUsageService extends GetxService { |
|
|
|
todayStats.value = todayStats.value.copyWith( |
|
|
|
ttsApiCalls: todayStats.value.ttsApiCalls + 1, |
|
|
|
ttsTextCharacters: todayStats.value.ttsTextCharacters + text.length, |
|
|
|
totalSessionDuration: todayStats.value.totalSessionDuration + durationSeconds, |
|
|
|
totalSessionDuration: |
|
|
|
todayStats.value.totalSessionDuration + durationSeconds, |
|
|
|
estimatedCost: _calculateEstimatedCost(todayStats.value.copyWith( |
|
|
|
ttsApiCalls: todayStats.value.ttsApiCalls + 1, |
|
|
|
ttsTextCharacters: todayStats.value.ttsTextCharacters + text.length, |
|
|
|
@ -252,7 +270,7 @@ class TranslationUsageService extends GetxService { |
|
|
|
); |
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
|
|
|
|
|
|
|
|
|
Logger.info('记录TTS API调用: 文本${text.length}字符, 时长${durationSeconds}秒'); |
|
|
|
} catch (e) { |
|
|
|
Logger.error('记录TTS API调用失败: $e'); |
|
|
|
@ -271,7 +289,7 @@ class TranslationUsageService extends GetxService { |
|
|
|
); |
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
|
|
|
|
|
|
|
|
|
Logger.info('记录AI摘要API调用'); |
|
|
|
} catch (e) { |
|
|
|
Logger.error('记录AI摘要API调用失败: $e'); |
|
|
|
@ -281,12 +299,12 @@ class TranslationUsageService extends GetxService { |
|
|
|
/// 开始新会话 |
|
|
|
void startNewSession() { |
|
|
|
realtimeStats.value.reset(); |
|
|
|
|
|
|
|
|
|
|
|
// 更新当日会话数 |
|
|
|
todayStats.value = todayStats.value.copyWith( |
|
|
|
sessionCount: todayStats.value.sessionCount + 1, |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
_saveTodayStats(); |
|
|
|
Logger.info('开始新的翻译会话'); |
|
|
|
} |
|
|
|
@ -296,23 +314,28 @@ class TranslationUsageService extends GetxService { |
|
|
|
double cost = 0.0; |
|
|
|
|
|
|
|
// 翻译成本:基于源文本字符数 |
|
|
|
cost += (stats.sourceTextCharacters / 1000) * (costConfig['translation_per_1k_chars'] ?? 0.05); |
|
|
|
cost += (stats.sourceTextCharacters / 1000) * |
|
|
|
(costConfig['translation_per_1k_chars'] ?? 0.05); |
|
|
|
|
|
|
|
// ASR成本:基于有效音频时长 |
|
|
|
cost += (stats.effectiveAudioDuration / 60) * (costConfig['asr_per_minute'] ?? 0.02); |
|
|
|
cost += (stats.effectiveAudioDuration / 60) * |
|
|
|
(costConfig['asr_per_minute'] ?? 0.02); |
|
|
|
|
|
|
|
// TTS成本:基于TTS文本字符数 |
|
|
|
cost += (stats.ttsTextCharacters / 1000) * (costConfig['tts_per_1k_chars'] ?? 0.03); |
|
|
|
cost += (stats.ttsTextCharacters / 1000) * |
|
|
|
(costConfig['tts_per_1k_chars'] ?? 0.03); |
|
|
|
|
|
|
|
// AI摘要成本(已废弃但保留兼容性) |
|
|
|
cost += stats.aiSummaryApiCalls * (costConfig['ai_summary_per_request'] ?? 0.01); |
|
|
|
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); |
|
|
|
return (sourceCharacters / 1000) * |
|
|
|
(costConfig['translation_per_1k_chars'] ?? 0.05); |
|
|
|
} |
|
|
|
|
|
|
|
/// 计算ASR成本(单次调用) |
|
|
|
@ -329,7 +352,8 @@ class TranslationUsageService extends GetxService { |
|
|
|
void _saveTodayStats() { |
|
|
|
try { |
|
|
|
final today = DateTime.now(); |
|
|
|
final todayKey = '${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; |
|
|
|
final todayKey = |
|
|
|
'${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}'; |
|
|
|
_storage.write('${_dailyStatsKey}_$todayKey', todayStats.value.toJson()); |
|
|
|
} catch (e) { |
|
|
|
Logger.error('保存当日统计数据失败: $e'); |
|
|
|
@ -339,7 +363,7 @@ class TranslationUsageService extends GetxService { |
|
|
|
/// 检查成本阈值 |
|
|
|
void _checkCostThreshold() { |
|
|
|
final todayCost = todayStats.value.estimatedCost; |
|
|
|
|
|
|
|
|
|
|
|
if (todayCost >= dailyCostThreshold.value) { |
|
|
|
_showCostWarning('今日翻译成本已达到预警阈值:¥${todayCost.toStringAsFixed(2)}'); |
|
|
|
} |
|
|
|
@ -364,19 +388,20 @@ class TranslationUsageService extends GetxService { |
|
|
|
}) async { |
|
|
|
try { |
|
|
|
final List<TranslationUsageStats> historyStats = []; |
|
|
|
|
|
|
|
for (DateTime date = startDate; |
|
|
|
date.isBefore(endDate) || date.isAtSameMomentAs(endDate); |
|
|
|
date = date.add(const Duration(days: 1))) { |
|
|
|
|
|
|
|
final dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; |
|
|
|
|
|
|
|
for (DateTime date = startDate; |
|
|
|
date.isBefore(endDate) || date.isAtSameMomentAs(endDate); |
|
|
|
date = date.add(const Duration(days: 1))) { |
|
|
|
final dateKey = |
|
|
|
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; |
|
|
|
final statsData = _storage.read('${_dailyStatsKey}_$dateKey'); |
|
|
|
|
|
|
|
|
|
|
|
if (statsData != null) { |
|
|
|
historyStats.add(TranslationUsageStats.fromJson(Map<String, dynamic>.from(statsData))); |
|
|
|
historyStats.add(TranslationUsageStats.fromJson( |
|
|
|
Map<String, dynamic>.from(statsData))); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return historyStats; |
|
|
|
} catch (e) { |
|
|
|
Logger.error('获取历史统计数据失败: $e'); |
|
|
|
@ -486,7 +511,8 @@ class TranslationUsageService extends GetxService { |
|
|
|
|
|
|
|
// 合并语言对使用统计 |
|
|
|
stats.languagePairUsage.forEach((key, value) { |
|
|
|
combinedLanguagePairUsage[key] = (combinedLanguagePairUsage[key] ?? 0) + value; |
|
|
|
combinedLanguagePairUsage[key] = |
|
|
|
(combinedLanguagePairUsage[key] ?? 0) + value; |
|
|
|
}); |
|
|
|
|
|
|
|
// 合并模式使用统计 |
|
|
|
|