diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index d5a1747ce..c83f11906 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -244,36 +244,37 @@ class Api { ); } - /// 资源使用上报(`/api/home/user_usages`) + /// 资源使用/用量上报接口(`/api/home/user_usages`) /// - /// 用途:在 AI 聊天等场景中,上报本次会话的消耗与功能调用次数,服务端据此统计并更新积分。 + /// 用途:上报翻译(Translation)和会议(Meet)的用量,服务端据此统计并回传最新余额/积分。 /// - /// 请求方式:`POST /api/home/user_usages` - /// - /// 认证:已通过全局拦截器自动携带 `Authorization`(见 `AuthInterceptor`)。 + /// 当前在 Dart 侧的使用场景: + /// - 会议助手 ASR:`UsageStatsService._reportMeetingUsageToServer` + /// - 翻译/同传 ASR:`UsageStatsService._reportAsrUsageToServer` /// /// 参数(`params`)结构: - /// - `usagetype`:`int` 类型,使用场景类型;Android 端 Agent 使用 `3` 表示 AICHAT。 + /// - `usagetype`:`int` 类型,使用场景类型(对应 `UsageType.toInt`),目前仅使用: + /// - `1`:翻译(Translation) + /// - `2`:会议(Meet) /// - `usages`:`Map`,各项指标的键值对,例如: - /// - `AICHAT_PROMPT_TOKENS`:本次对话的提示词 Token 数 - /// - `AICHAT_COMPLETION_TOKENS`:本次对话的生成 Token 数 - /// - `AICHAT_FUNCTION_{函数名}`:被调用的函数次数(按函数名累加) + /// - `MEETING_ASR_AUDIO_SECONDS`:会议识别音频时长(秒) + /// - `TRANSLATION_MODE_{mode}`:某翻译模式下累计有效识别秒数 + /// - `TRANSLATION_ASR_LANGUAGE_{language}`:某语言/语言对下的调用次数 /// - /// 返回:标准封装后 `code == 0` 时返回 `data`。其中 `data.usages` 可能包含键 `"3"`,表示最新的 AI 积分值。 + /// 返回: + /// - 经过 `AuthInterceptor` 处理后,本方法返回值即为服务端响应体中的 `data` 字段。 + /// - 若上报成功,`data['usages']` 为 `Map`,内部各键的含义与积分/余额更新规则由服务端约定。 /// - /// 使用示例: + /// 使用示例(Dart): /// ```dart /// final params = { - /// 'usagetype': 3, // AICHAT + /// 'usagetype': UsageType.Meet.toInt, /// 'usages': { - /// 'AICHAT_PROMPT_TOKENS': 120, - /// 'AICHAT_COMPLETION_TOKENS': 256, - /// 'AICHAT_FUNCTION_toolX': 1, + /// 'MEETING_ASR_AUDIO_SECONDS': 120, /// } /// }; /// final data = await Api.usagesResPoints(params); /// final usages = (data['usages'] as Map?)?.cast(); - /// final aiIntegral = usages?['3']; // 服务端返回的最新 AI 积分(如有) /// ``` static usagesResPoints([params]) { return DioManager().request( diff --git a/lib/data/services/usage_stats_service.dart b/lib/data/services/usage_stats_service.dart index e0f76a799..969e2ea55 100644 --- a/lib/data/services/usage_stats_service.dart +++ b/lib/data/services/usage_stats_service.dart @@ -673,6 +673,9 @@ class UsageStatsService extends GetxService { } } + /// 获取会议历史统计 + /// + /// 从[startDate]到[endDate]的会议ASR记录。 Future> getMeetingHistoryStats({ required DateTime startDate, required DateTime endDate, @@ -697,6 +700,9 @@ class UsageStatsService extends GetxService { } } + /// 聚合会议统计 + /// + /// 从[daily]中聚合会议统计数据。 MeetingAsrHistoryStats aggregateMeetingStats( List daily) { int calls = 0; @@ -719,4 +725,59 @@ class UsageStatsService extends GetxService { allTranscriptBytes: transcriptBytes, ); } + + /// 获取会议对象存储历史统计 + /// + /// 从[startDate]到[endDate]的会议对象存储上传记录。 + Future> getMeetingStorageHistoryStats({ + required DateTime startDate, + required DateTime endDate, + }) async { + try { + final List list = []; + for (DateTime date = startDate; + date.isBefore(endDate) || date.isAtSameMomentAs(endDate); + date = date.add(const Duration(days: 1))) { + final key = _formatDate(date); + final raw = _storage.read('${_meetingOsDailykey}_$key'); + List uploads = []; + if (raw is List) { + uploads = raw + .map((e) => MeetingObjectStorageRecord.fromJson( + Map.from(e))) + .toList(); + } + if (uploads.isNotEmpty) { + list.add(MeetingStorageDailyStats( + date: key, userId: getCurrentUserId(), items: uploads)); + } + } + Logger.i('MeetingOSrecord', + '获取会议对象存储历史: start=${_formatDate(startDate)}, end=${_formatDate(endDate)}, 有效天数=${list.length}'); + return list; + } catch (e) { + Logger.error('获取对象存储历史统计失败: $e'); + return []; + } + } + + MeetingStorageHistoryStats aggregateMeetingStorageStats( + List daily) { + int uploads = 0; + int storageBytes = 0; + int transferBytes = 0; + for (final d in daily) { + uploads += d.storageUploads; + storageBytes += d.storageBytesTotal; + transferBytes += d.transferBytesTotal; + } + Logger.i('MeetingOSrecord', + '聚合对象存储统计: uploads=${uploads}, storageBytes=${storageBytes}, transferBytes=${transferBytes}'); + return MeetingStorageHistoryStats( + userId: getCurrentUserId(), + allStorageUploads: uploads, + allStorageBytes: storageBytes, + allTransferBytes: transferBytes, + ); + } } diff --git a/lib/data/services/version_update_service.dart b/lib/data/services/version_update_service.dart index 4c37e851f..b25f861b2 100644 --- a/lib/data/services/version_update_service.dart +++ b/lib/data/services/version_update_service.dart @@ -124,16 +124,15 @@ class VersionUpdateService extends GetxService { //_channel = platformInfo.channel; _lastCheckTime = DateTime.now(); - // 调用后端接口获取最新版本信息 - final response = - await Api.getChannelApp({'channel': platformInfo.channel.value}); - // final response = await Api.getChannelApps(); + // 获取所有渠道包信息 + final response = await Api.getChannelApps(); - _logger.d('lxm--获取渠道包信息:$response'); + _logger.d('lxm--获取所有渠道包信息:$response'); - if (response == null || !response.containsKey('app')) { - //[2]获取渠道包信息失败: 响应为空或不包含app字段 - _logger.w('lxm--获取渠道包信息失败: 响应为空或不包含app字段'); + final List apps = _parseAppsList(response); + + if (apps.isEmpty) { + _logger.w('lxm--获取所有渠道包信息失败或为空'); return VersionUpdateInfo( hasUpdate: false, currentVersion: currentVersion, @@ -142,24 +141,38 @@ class VersionUpdateService extends GetxService { ); } - //final channelAppResponse = ChannelAppResponse.fromJson(response); - // 直接从response中获取app信息 - final appInfo = AppInfo.fromJson(response['app']); - _appInfo = appInfo; - _logger.d('获取到的最新版本信息: ${appInfo.toJson()}'); - _latestVersion = appInfo.version; - _updateUrl = appInfo.address; + final Map appIndex = _buildChannelIndex(apps); - // 设置游客登录显示状态(先用当前渠道结果作为回退,再用.env渠道覆盖以保持一致) - _isShowTourists = appInfo.tourists; - _allowSkipDeviceBinding = appInfo.allowskipdevicebinding; + // 依据设备品牌渠道用于版本更新链接 + final brandChannel = platformInfo.channel; - //print('lxm--_isShowTourists: $_isShowTourists'); - if (Platform.isAndroid && _channel != platformInfo.channel) { - //[4]当前渠道与.env渠道不同,需要从.env渠道获取游客登录配置 - await _refreshTouristsFromEnv(); + // 环境变量渠道用于游客登录配置 + final envChannelName = dotenv.env['Channel'] ?? 'OFFICIAL'; + final envChannel = UserChannel.fromName(envChannelName); + final AppInfo? envAppInfo = appIndex[envChannel]; + + // 若不存在品牌渠道信息,降级为环境渠道;仍不存在则返回无更新 + final AppInfo? chosenAppInfo = + _getChannelAppInfo(appIndex, brandChannel, envChannel); + if (chosenAppInfo == null) { + _logger.w('lxm--未找到品牌或环境渠道的应用信息,返回无更新'); + return VersionUpdateInfo( + hasUpdate: false, + currentVersion: currentVersion, + latestVersion: currentVersion, + updateType: UpdateType.none, + ); } + _appInfo = chosenAppInfo; + _latestVersion = chosenAppInfo.version; + _updateUrl = chosenAppInfo.address; + + // 设置游客登录显示状态(先用当前渠道结果作为回退,再用.env渠道覆盖以保持一致) + _isShowTourists = (envAppInfo ?? chosenAppInfo).tourists; + _allowSkipDeviceBinding = + (envAppInfo ?? chosenAppInfo).allowskipdevicebinding; + _logger.d('lxm--从服务器获取游客登录配置(最终): ${_isShowTourists}'); // if (channelAppResponse.code != 0 || channelAppResponse.data == null) { @@ -173,20 +186,21 @@ class VersionUpdateService extends GetxService { // } // final appInfo = channelAppResponse.data!.app; - _hasUpdate = _compareVersions(currentVersion, appInfo.version); + _hasUpdate = _compareVersions(currentVersion, chosenAppInfo.version); // 判断更新类型:如果主版本号不同则强制更新 UpdateType updateType = UpdateType.none; if (_hasUpdate!) { - _isForceUpdate = _shouldForceUpdate(currentVersion, appInfo.version); + _isForceUpdate = + _shouldForceUpdate(currentVersion, chosenAppInfo.version); updateType = _isForceUpdate! ? UpdateType.force : UpdateType.optional; } //[3]返回版本更新信息 return VersionUpdateInfo( hasUpdate: _hasUpdate!, currentVersion: currentVersion.split('+')[0], - latestVersion: appInfo.version.split('+')[0], - appInfo: appInfo, + latestVersion: chosenAppInfo.version.split('+')[0], + appInfo: chosenAppInfo, updateType: updateType, ); } catch (e) { @@ -296,33 +310,6 @@ class VersionUpdateService extends GetxService { } } - /// 从.env文件刷新游客登录配置 - Future _refreshTouristsFromEnv() async { - try { - final envChannelName = dotenv.env['Channel']; - if (envChannelName == null || envChannelName.isEmpty) { - _logger.w('未配置.env Channel,沿用当前渠道的游客配置: ${_isShowTourists}'); - return; - } - final envChannel = UserChannel.fromName(envChannelName); - - final resp = await Api.getChannelApp({'channel': envChannel.value}); - // _logger.d('lxm--获取渠道包信息:$resp'); - if (resp != null && resp.containsKey('app')) { - final envAppInfo = AppInfo.fromJson(resp['app']); - // print('lxm--envAppInfo: ${envAppInfo.toJson()}'); - _isShowTourists = envAppInfo.tourists; - _allowSkipDeviceBinding = envAppInfo.allowskipdevicebinding; - _logger.d( - 'lxm--游客登录配置已按.env${envChannel.value}渠道同步: ${envAppInfo.tourists} (channel=$envChannelName),_isShowTourists: $_isShowTourists,_allowSkipDeviceBinding: $_allowSkipDeviceBinding'); - } else { - _logger.w('按.env渠道获取游客配置失败:响应为空或不含app字段'); - } - } catch (e) { - _logger.w('按.env渠道同步游客配置失败: $e'); - } - } - /// 获取iOS设备品牌信息 Future _getIosDeviceBrand() async { try { @@ -415,6 +402,45 @@ class VersionUpdateService extends GetxService { return latestVersions.length > currentVersions.length; } + /// 解析响应数据为AppInfo列表 + List _parseAppsList(dynamic response) { + try { + final appsJson = (response is Map) ? response['apps'] : null; + if (appsJson is List) { + return appsJson + .whereType>() + .map((e) => AppInfo.fromJson(e)) + .toList(); + } + } catch (e) { + _logger.e('解析渠道列表失败: $e'); + } + return []; + } + + /// 构建渠道索引 + Map _buildChannelIndex(List apps) { + final index = {}; + for (final app in apps) { + index[app.channel] = app; + } + return index; + } + + /// 获取渠道信息,带备选策略 + /// + /// 优先返回主渠道的信息,如果主渠道不存在则返回备选渠道的信息。 + /// 主要用于: + /// 1. 版本更新:优先使用设备品牌渠道,备选使用环境变量渠道 + /// 2. 功能配置:优先使用环境变量渠道,备选使用版本更新渠道 + AppInfo? _getChannelAppInfo( + Map index, + UserChannel primary, + UserChannel? fallback, + ) { + return index[primary] ?? (fallback != null ? index[fallback] : null); + } + /// 解析版本号字符串为数字数组 List _parseVersion(String version) { final parts = version.split('.'); diff --git a/lib/modules/usage/controllers/meeting_stats_controller.dart b/lib/modules/usage/controllers/meeting_stats_controller.dart index 147c709c7..2aad0d20a 100644 --- a/lib/modules/usage/controllers/meeting_stats_controller.dart +++ b/lib/modules/usage/controllers/meeting_stats_controller.dart @@ -17,23 +17,49 @@ class MeetingStatsController extends GetxController { allAudioFileBytes: 0, allTranscriptBytes: 0, ).obs; - final Rx weekStats = MeetingAsrHistoryStats( + final Rx historyStats = MeetingAsrHistoryStats( userId: '', allAsrCalls: 0, allAudioSeconds: 0, allAudioFileBytes: 0, allTranscriptBytes: 0, ).obs; - final Rx monthStats = MeetingAsrHistoryStats( + + final RxList historyDaily = + [].obs; + + final Rx todayStorageStats = + MeetingStorageHistoryStats( + userId: '', + allStorageUploads: 0, + allStorageBytes: 0, + allTransferBytes: 0, + ).obs; + + final Rx historyStorageStats = + MeetingStorageHistoryStats( + userId: '', + allStorageUploads: 0, + allStorageBytes: 0, + allTransferBytes: 0, + ).obs; + + final Rx rangeStart = Rx(null); + final Rx rangeEnd = Rx(null); + final Rx rangeStats = MeetingAsrHistoryStats( userId: '', allAsrCalls: 0, allAudioSeconds: 0, allAudioFileBytes: 0, allTranscriptBytes: 0, ).obs; - - final RxList historyDaily = - [].obs; + final Rx rangeStorageStats = + MeetingStorageHistoryStats( + userId: '', + allStorageUploads: 0, + allStorageBytes: 0, + allTransferBytes: 0, + ).obs; @override void onInit() { @@ -52,28 +78,53 @@ class MeetingStatsController extends GetxController { ); todayStats.value = _usageService.aggregateMeetingStats(todayList); - final weekStart = now.subtract(Duration(days: now.weekday - 1)); - final weekEnd = weekStart.add(const Duration(days: 6)); - final weekList = await _usageService.getMeetingHistoryStats( - startDate: DateTime(weekStart.year, weekStart.month, weekStart.day), - endDate: DateTime(weekEnd.year, weekEnd.month, weekEnd.day), + final historyStart = now.subtract(const Duration(days: 7)); + historyDaily.value = await _usageService.getMeetingHistoryStats( + startDate: + DateTime(historyStart.year, historyStart.month, historyStart.day), + endDate: DateTime(now.year, now.month, now.day), ); - weekStats.value = _usageService.aggregateMeetingStats(weekList); + historyStats.value = + _usageService.aggregateMeetingStats(historyDaily.value); - final monthStart = DateTime(now.year, now.month, 1); - final monthEnd = DateTime(now.year, now.month + 1, 0); - final monthList = await _usageService.getMeetingHistoryStats( - startDate: monthStart, - endDate: monthEnd, + final todayStorageDaily = + await _usageService.getMeetingStorageHistoryStats( + startDate: DateTime(now.year, now.month, now.day), + endDate: DateTime(now.year, now.month, now.day), ); - monthStats.value = _usageService.aggregateMeetingStats(monthList); + todayStorageStats.value = + _usageService.aggregateMeetingStorageStats(todayStorageDaily); - final historyStart = now.subtract(const Duration(days: 7)); - historyDaily.value = await _usageService.getMeetingHistoryStats( + final historyStorageDaily = + await _usageService.getMeetingStorageHistoryStats( startDate: DateTime(historyStart.year, historyStart.month, historyStart.day), endDate: DateTime(now.year, now.month, now.day), ); + historyStorageStats.value = + _usageService.aggregateMeetingStorageStats(historyStorageDaily); + + if (rangeStart.value != null && rangeEnd.value != null) { + final customAsrDaily = await _usageService.getMeetingHistoryStats( + startDate: DateTime(rangeStart.value!.year, rangeStart.value!.month, + rangeStart.value!.day), + endDate: DateTime( + rangeEnd.value!.year, rangeEnd.value!.month, rangeEnd.value!.day), + ); + rangeStats.value = _usageService.aggregateMeetingStats(customAsrDaily); + final customStorageDaily = + await _usageService.getMeetingStorageHistoryStats( + startDate: DateTime(rangeStart.value!.year, rangeStart.value!.month, + rangeStart.value!.day), + endDate: DateTime( + rangeEnd.value!.year, rangeEnd.value!.month, rangeEnd.value!.day), + ); + rangeStorageStats.value = + _usageService.aggregateMeetingStorageStats(customStorageDaily); + } else { + rangeStats.value = historyStats.value; + rangeStorageStats.value = historyStorageStats.value; + } Logger.info('会议统计数据加载完成'); } catch (e) { @@ -90,14 +141,11 @@ class MeetingStatsController extends GetxController { selectedTimeRange.value = 'today'.tr; // 今日 break; case 1: - selectedTimeRange.value = 'thisWeek'.tr; // 本周 - break; - case 2: - selectedTimeRange.value = 'thisMonth'.tr; // 本月 - break; - case 3: selectedTimeRange.value = 'history'.tr; // 历史 break; + default: + selectedTimeRange.value = 'today'.tr; // 今日 + break; } } @@ -106,9 +154,7 @@ class MeetingStatsController extends GetxController { case 0: return todayStats.value; case 1: - return weekStats.value; - case 2: - return monthStats.value; + return rangeStats.value; default: return todayStats.value; } @@ -147,4 +193,34 @@ class MeetingStatsController extends GetxController { Future refreshData() async { await loadAllStats(); } + + Future setCustomRangeDates(DateTime start, DateTime end) async { + rangeStart.value = start; + rangeEnd.value = end; + isLoading.value = true; + try { + final asrDaily = await _usageService.getMeetingHistoryStats( + startDate: DateTime(start.year, start.month, start.day), + endDate: DateTime(end.year, end.month, end.day), + ); + rangeStats.value = _usageService.aggregateMeetingStats(asrDaily); + final storageDaily = await _usageService.getMeetingStorageHistoryStats( + startDate: DateTime(start.year, start.month, start.day), + endDate: DateTime(end.year, end.month, end.day), + ); + rangeStorageStats.value = + _usageService.aggregateMeetingStorageStats(storageDaily); + } catch (e) { + Logger.error('设置自定义日期范围失败: $e'); + } finally { + isLoading.value = false; + } + } + + String formatDate(DateTime d) { + final y = d.year.toString(); + final m = d.month.toString().padLeft(2, '0'); + final day = d.day.toString().padLeft(2, '0'); + return '$y-$m-$day'; + } } diff --git a/lib/modules/usage/views/meeting_stats_view.dart b/lib/modules/usage/views/meeting_stats_view.dart index 140a40447..0702a4aa1 100644 --- a/lib/modules/usage/views/meeting_stats_view.dart +++ b/lib/modules/usage/views/meeting_stats_view.dart @@ -4,6 +4,7 @@ import 'package:get/get.dart'; import '../../../core/theme/app_colors.dart'; import '../controllers/meeting_stats_controller.dart'; import '../../../data/models/user_Info.dart'; +import '../../../data/models/meeting_usage_stats.dart'; class MeetingStatsView extends GetView { const MeetingStatsView({Key? key}) : super(key: key); @@ -14,14 +15,14 @@ class MeetingStatsView extends GetView { return Scaffold( backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], - appBar: _buildAppBar(isDarkMode), + appBar: _buildAppBar(context, isDarkMode), body: Obx(() => controller.isLoading.value ? const Center(child: CircularProgressIndicator()) : _buildBody(isDarkMode)), ); } - PreferredSizeWidget _buildAppBar(bool isDarkMode) { + PreferredSizeWidget _buildAppBar(BuildContext context, bool isDarkMode) { return AppBar( leading: IconButton( icon: Icon(Icons.arrow_back_ios, @@ -39,6 +40,27 @@ class MeetingStatsView extends GetView { ), centerTitle: true, actions: [ + IconButton( + icon: Icon(Icons.date_range, + color: isDarkMode ? Colors.white : Colors.black), + onPressed: () async { + final now = DateTime.now(); + final initialStart = controller.rangeStart.value ?? + DateTime(now.year, now.month, now.day); + final initialEnd = controller.rangeEnd.value ?? + DateTime(now.year, now.month, now.day); + final picked = await showDateRangePicker( + context: context, + firstDate: DateTime(now.year - 1), + lastDate: DateTime(now.year + 1), + initialDateRange: + DateTimeRange(start: initialStart, end: initialEnd), + ); + if (picked != null) { + await controller.setCustomRangeDates(picked.start, picked.end); + } + }, + ), IconButton( icon: Icon(Icons.refresh, color: isDarkMode ? Colors.white : Colors.black), @@ -49,16 +71,32 @@ class MeetingStatsView extends GetView { } Widget _buildBody(bool isDarkMode) { + final hasRange = controller.rangeStart.value != null && + controller.rangeEnd.value != null; + final historyTitle = hasRange + ? "${'history'.tr} (${controller.formatDate(controller.rangeStart.value!)} ~ ${controller.formatDate(controller.rangeEnd.value!)})" + : 'history'.tr; return Column( children: [ _buildIntegralRow(isDarkMode), - _buildTimeRangeSelector(isDarkMode), Expanded( child: SingleChildScrollView( padding: EdgeInsets.all(16.w), child: Column( children: [ - _buildOverviewCard(isDarkMode), + _buildOverviewCard( + 'today'.tr, // 今日 + isDarkMode, + controller.todayStats.value, + controller.todayStorageStats.value, + ), + SizedBox(height: 16.h), + _buildOverviewCard( + historyTitle, + isDarkMode, + controller.rangeStats.value, + controller.rangeStorageStats.value, + ), ], ), ), @@ -112,60 +150,12 @@ class MeetingStatsView extends GetView { }); } - Widget _buildTimeRangeSelector(bool isDarkMode) { - return Container( - margin: EdgeInsets.all(16.w), - decoration: BoxDecoration( - color: isDarkMode ? AppColors.cardBackground : Colors.white, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 10, - offset: const Offset(0, 2)), - ], - ), - child: Obx(() => Row( - children: [ - _buildTimeRangeTab('today'.tr, 0, isDarkMode), // 今日 - _buildTimeRangeTab('thisWeek'.tr, 1, isDarkMode), // 本周 - _buildTimeRangeTab('thisMonth'.tr, 2, isDarkMode), // 本月 - _buildTimeRangeTab('history'.tr, 3, isDarkMode), // 历史 - ], - )), - ); - } - - Widget _buildTimeRangeTab(String title, int index, bool isDarkMode) { - final isSelected = controller.selectedTabIndex.value == index; - - return Expanded( - child: GestureDetector( - onTap: () => controller.switchTimeRange(index), - child: Container( - padding: EdgeInsets.symmetric(vertical: 12.h), - decoration: BoxDecoration( - color: isSelected ? AppColors.primary : Colors.transparent, - borderRadius: BorderRadius.circular(12.r), - ), - child: Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14.sp, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, - color: isSelected - ? Colors.white - : (isDarkMode ? Colors.white70 : Colors.black54), - ), - ), - ), - ), - ); - } - - Widget _buildOverviewCard(bool isDarkMode) { - final stats = controller.currentStats; + Widget _buildOverviewCard( + String title, + bool isDarkMode, + MeetingAsrHistoryStats asrStats, + MeetingStorageHistoryStats storageStats, + ) { return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( @@ -185,13 +175,13 @@ class MeetingStatsView extends GetView { children: [ Icon(Icons.dashboard, color: AppColors.primary, size: 20.sp), SizedBox(width: 8.w), - Obx(() => Text( - '${controller.selectedTimeRange.value}${'overview'.tr}', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black), - )), + Text( + '$title${'overview'.tr}', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black), + ), ], ), SizedBox(height: 16.h), @@ -200,13 +190,13 @@ class MeetingStatsView extends GetView { Expanded( child: _buildStatItem( 'asrApiCalls'.tr, // ASR调用次数 - controller.formatNumber(stats.allAsrCalls), + controller.formatNumber(asrStats.allAsrCalls), Icons.api, isDarkMode)), Expanded( child: _buildStatItem( 'totalAudioDuration'.tr, // 音频总时长 - controller.formatDuration(stats.allAudioSeconds), + controller.formatDuration(asrStats.allAudioSeconds), Icons.mic, isDarkMode)), ], @@ -217,17 +207,34 @@ class MeetingStatsView extends GetView { Expanded( child: _buildStatItem( 'audioFileSize'.tr, // 音频文件大小 - controller.formatBytes(stats.allAudioFileBytes), + controller.formatBytes(asrStats.allAudioFileBytes), Icons.folder, isDarkMode)), Expanded( child: _buildStatItem( 'transcriptBytes'.tr, // 转写字节 - controller.formatNumber(stats.allTranscriptBytes), + controller.formatNumber(asrStats.allTranscriptBytes), Icons.text_snippet, isDarkMode)), ], ), + SizedBox(height: 12.h), + Row( + children: [ + Expanded( + child: _buildStatItem( + 'storageUploads'.tr, // 对象存储上传次数 + controller.formatNumber(storageStats.allStorageUploads), + Icons.cloud_upload, + isDarkMode)), + Expanded( + child: _buildStatItem( + 'storageBytes'.tr, // 对象存储总大小 + controller.formatBytes(storageStats.allStorageBytes), + Icons.cloud, + isDarkMode)), + ], + ), ], ), ); diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 65de6b6c0..c85b61e4e 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -135,6 +135,7 @@ object AgentService : CoroutineScope { // AI流生成相关 private var currentAiJob: Job? = null private var currsessionId: String = "" + private var currsessionAsrId: String = "" private var vipExptime:Int = 0 @@ -685,13 +686,14 @@ object AgentService : CoroutineScope { ) ) }else{ - text = recognitionResult + recognizing - if(recognitionResult.isEmpty()){ - currsessionId = sessionid + if(currsessionAsrId.isEmpty()) { + currsessionAsrId = sessionid } + text = recognitionResult + recognizing + sendEvent( "recognizing", mapOf( - "sessionid" to currsessionId, + "sessionid" to currsessionAsrId, "text" to text, "language" to detectedLanguage ) @@ -740,20 +742,31 @@ object AgentService : CoroutineScope { ) ) }else{ + if (currsessionAsrId.isEmpty()) { + currsessionAsrId = sessionid + } recognitionResult += result pendingProcessJob?.cancel() + sendEvent( + "recognizing", mapOf( + "sessionid" to currsessionAsrId, + "text" to recognitionResult, + "language" to detectedLanguage + ) + ) pendingProcessJob = launch { delay(2000) if (recognitionResult.isNotEmpty()) { sendEvent( "recognition_result", mapOf( - "sessionid" to currsessionId, + "sessionid" to currsessionAsrId, "text" to recognitionResult, "language" to detectedLanguage ) ) - processWithChatApi(currsessionId, recognitionResult) + processWithChatApi(currsessionAsrId, recognitionResult) recognitionResult = "" + currsessionAsrId = "" } } }