You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

322 lines
8.9 KiB

import 'package:get/get.dart';
import '../../../data/services/usage_stats_service.dart';
import '../../../data/models/translation_usage_stats.dart';
import '../../../core/utils/logger.dart';
class TranslationStatsController extends GetxController {
final UsageStatsService _usageService = Get.find<UsageStatsService>();
final isLoading = false.obs;
final selectedTabIndex = 0.obs;
final selectedTimeRange = '今日'.obs;
final Rx<TranslationUsageStats> todayStats =
TranslationUsageStats.empty('').obs;
final Rx<TranslationUsageStats> weekStats =
TranslationUsageStats.empty('').obs;
final Rx<TranslationUsageStats> monthStats =
TranslationUsageStats.empty('').obs;
final RxList<TranslationUsageStats> historyStats =
<TranslationUsageStats>[].obs;
RealtimeUsageStats get realtimeStats => _usageService.realtimeStats.value;
final RxMap<String, double> costConfig = <String, double>{}.obs;
final RxDouble dailyCostThreshold = 10.0.obs;
final RxDouble monthlyCostThreshold = 300.0.obs;
final RxList<Map<String, dynamic>> chartData = <Map<String, dynamic>>[].obs;
@override
void onInit() {
super.onInit();
_initializeData();
}
void _initializeData() {
ever(_usageService.todayStats, (stats) {
todayStats.value = stats;
_updateChartData();
});
ever(_usageService.costConfig, (config) {
costConfig.value = config;
});
ever(_usageService.dailyCostThreshold, (threshold) {
dailyCostThreshold.value = threshold;
});
ever(_usageService.monthlyCostThreshold, (threshold) {
monthlyCostThreshold.value = threshold;
});
loadAllStats();
}
Future<void> loadAllStats() async {
isLoading.value = true;
try {
todayStats.value = _usageService.todayStats.value;
await _loadWeekStats();
await _loadMonthStats();
await _loadHistoryStats();
_updateChartData();
Logger.info('统计数据加载完成');
} catch (e) {
Logger.error('加载统计数据失败: $e');
Get.snackbar(
'加载失败',
'统计数据加载失败,请稍后重试',
snackPosition: SnackPosition.TOP,
backgroundColor: Get.theme.colorScheme.error.withValues(alpha: 0.1),
colorText: Get.theme.colorScheme.error,
);
} finally {
isLoading.value = false;
}
}
Future<void> _loadWeekStats() async {
try {
final now = DateTime.now();
final weekStart = now.subtract(Duration(days: now.weekday - 1));
final weekEnd = weekStart.add(const Duration(days: 6));
final dailyStats = await _usageService.getHistoryStats(
startDate: weekStart,
endDate: weekEnd,
);
weekStats.value = _aggregateStats(dailyStats, 'week');
} catch (e) {
Logger.error('加载本周统计失败: $e');
}
}
Future<void> _loadMonthStats() async {
try {
final now = DateTime.now();
monthStats.value =
await _usageService.getMonthlyStats(now.year, now.month);
} catch (e) {
Logger.error('加载本月统计失败: $e');
}
}
Future<void> _loadHistoryStats() async {
try {
final endDate = DateTime.now();
final startDate = endDate.subtract(const Duration(days: 7));
historyStats.value = await _usageService.getHistoryStats(
startDate: startDate,
endDate: endDate,
);
historyStats.sort((a, b) => b.date.compareTo(a.date));
} catch (e) {
Logger.error('加载历史统计失败: $e');
}
}
TranslationUsageStats _aggregateStats(
List<TranslationUsageStats> statsList, String period) {
if (statsList.isEmpty) {
return TranslationUsageStats.empty(_usageService.getCurrentUserId());
}
int totalTranslationCalls = 0;
int totalAsrCalls = 0;
int totalTtsCalls = 0;
int totalAiSummaryCalls = 0;
int totalSourceChars = 0;
int totalTargetChars = 0;
int totalRecordingDuration = 0;
int totalTtsPlayDuration = 0;
int totalSessions = 0;
double totalCost = 0.0;
final Map<String, int> combinedLanguagePairUsage = {};
final Map<String, int> combinedModeUsage = {};
for (final stats in statsList) {
totalTranslationCalls += stats.translationApiCalls;
totalAsrCalls += stats.asrApiCalls;
totalTtsCalls += stats.ttsApiCalls;
totalAiSummaryCalls += stats.aiSummaryApiCalls;
totalSourceChars += stats.sourceTextCharacters;
totalTargetChars += stats.ttsTextCharacters;
totalRecordingDuration += stats.effectiveAudioDuration;
totalTtsPlayDuration += stats.totalSessionDuration;
totalSessions += stats.sessionCount;
totalCost += stats.estimatedCost;
stats.languagePairUsage.forEach((key, value) {
combinedLanguagePairUsage[key] =
(combinedLanguagePairUsage[key] ?? 0) + value;
});
stats.modeUsage.forEach((key, value) {
combinedModeUsage[key] = (combinedModeUsage[key] ?? 0) + value;
});
}
return TranslationUsageStats(
id: '${period}_${DateTime.now().millisecondsSinceEpoch}',
date: DateTime.now(),
userId: 'current_user',
timeGranularity: period,
translationApiCalls: totalTranslationCalls,
asrApiCalls: totalAsrCalls,
ttsApiCalls: totalTtsCalls,
aiSummaryApiCalls: totalAiSummaryCalls,
sourceTextCharacters: totalSourceChars,
ttsTextCharacters: totalTargetChars,
effectiveAudioDuration: totalRecordingDuration,
totalSessionDuration: totalTtsPlayDuration,
sessionCount: totalSessions,
estimatedCost: totalCost,
);
}
void _updateChartData() {
final List<Map<String, dynamic>> data = [];
switch (selectedTabIndex.value) {
case 0:
data.add({
'label': '今日',
'apiCalls': todayStats.value.totalApiCalls,
'cost': todayStats.value.estimatedCost,
'characters': todayStats.value.totalCharacterCount,
});
break;
case 1:
data.add({
'label': '本周',
'apiCalls': weekStats.value.totalApiCalls,
'cost': weekStats.value.estimatedCost,
'characters': weekStats.value.totalCharacterCount,
});
break;
case 2:
data.add({
'label': '本月',
'apiCalls': monthStats.value.totalApiCalls,
'cost': monthStats.value.estimatedCost,
'characters': monthStats.value.totalCharacterCount,
});
break;
case 3:
for (final stats in historyStats.take(7)) {
data.add({
'label': '${stats.date.month}/${stats.date.day}',
'apiCalls': stats.totalApiCalls,
'cost': stats.estimatedCost,
'characters': stats.totalCharacterCount,
});
}
break;
}
chartData.value = data;
}
void switchTimeRange(int index) {
selectedTabIndex.value = index;
switch (index) {
case 0:
selectedTimeRange.value = '今日';
break;
case 1:
selectedTimeRange.value = '本周';
break;
case 2:
selectedTimeRange.value = '本月';
break;
case 3:
selectedTimeRange.value = '历史';
break;
}
_updateChartData();
}
TranslationUsageStats get currentStats {
switch (selectedTabIndex.value) {
case 0:
return todayStats.value;
case 1:
return weekStats.value;
case 2:
return monthStats.value;
default:
return todayStats.value;
}
}
Future<void> refreshData() async {
await loadAllStats();
}
void cleanupOldData() {
_usageService.cleanupOldData();
Get.snackbar(
'清理完成',
'已清理90天前的过期数据',
snackPosition: SnackPosition.TOP,
backgroundColor: Get.theme.colorScheme.primary.withValues(alpha: 0.1),
colorText: Get.theme.colorScheme.primary,
);
}
Future<void> exportStats() async {
try {
Get.snackbar(
'导出功能',
'数据导出功能正在开发中',
snackPosition: SnackPosition.TOP,
backgroundColor: Get.theme.colorScheme.primary.withValues(alpha: 0.1),
colorText: Get.theme.colorScheme.primary,
);
} catch (e) {
Logger.error('导出统计数据失败: $e');
}
}
String formatDuration(int seconds) {
if (seconds < 60) {
return '$seconds秒';
} else if (seconds < 3600) {
final minutes = seconds ~/ 60;
final remainingSeconds = seconds % 60;
return '$minutes分$remainingSeconds秒';
} else {
final hours = seconds ~/ 3600;
final minutes = (seconds % 3600) ~/ 60;
return '$hours小时$minutes分';
}
}
String formatCost(double cost) {
return '¥${cost.toStringAsFixed(2)}';
}
String formatNumber(int number) {
if (number < 1000) {
return number.toString();
} else if (number < 1000000) {
return '${(number / 1000).toStringAsFixed(1)}K';
} else {
return '${(number / 1000000).toStringAsFixed(1)}M';
}
}
}