Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
liwei1dao 10 months ago
parent
commit
aadadb36e3
  1. 33
      lib/data/services/network/api.dart
  2. 61
      lib/data/services/usage_stats_service.dart
  3. 132
      lib/data/services/version_update_service.dart
  4. 132
      lib/modules/usage/controllers/meeting_stats_controller.dart
  5. 145
      lib/modules/usage/views/meeting_stats_view.dart
  6. 25
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

33
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<String, int>`,各项指标的键值对,例如:
/// - `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<String, int>`,内部各键的含义与积分/余额更新规则由服务端约定。
///
/// 使用示例:
/// 使用示例(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<String, dynamic>();
/// final aiIntegral = usages?['3']; // 服务端返回的最新 AI 积分(如有)
/// ```
static usagesResPoints([params]) {
return DioManager().request(

61
lib/data/services/usage_stats_service.dart

@ -673,6 +673,9 @@ class UsageStatsService extends GetxService {
}
}
/// 获取会议历史统计
///
/// 从[startDate]到[endDate]的会议ASR记录。
Future<List<MeetingAsrDailyStats>> getMeetingHistoryStats({
required DateTime startDate,
required DateTime endDate,
@ -697,6 +700,9 @@ class UsageStatsService extends GetxService {
}
}
/// 聚合会议统计
///
/// 从[daily]中聚合会议统计数据。
MeetingAsrHistoryStats aggregateMeetingStats(
List<MeetingAsrDailyStats> daily) {
int calls = 0;
@ -719,4 +725,59 @@ class UsageStatsService extends GetxService {
allTranscriptBytes: transcriptBytes,
);
}
/// 获取会议对象存储历史统计
///
/// 从[startDate]到[endDate]的会议对象存储上传记录。
Future<List<MeetingStorageDailyStats>> getMeetingStorageHistoryStats({
required DateTime startDate,
required DateTime endDate,
}) async {
try {
final List<MeetingStorageDailyStats> 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<MeetingObjectStorageRecord> uploads = [];
if (raw is List) {
uploads = raw
.map((e) => MeetingObjectStorageRecord.fromJson(
Map<String, dynamic>.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<MeetingStorageDailyStats> 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,
);
}
}

132
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<AppInfo> 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<UserChannel, AppInfo> 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<void> _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<void> _getIosDeviceBrand() async {
try {
@ -415,6 +402,45 @@ class VersionUpdateService extends GetxService {
return latestVersions.length > currentVersions.length;
}
/// 解析响应数据为AppInfo列表
List<AppInfo> _parseAppsList(dynamic response) {
try {
final appsJson = (response is Map) ? response['apps'] : null;
if (appsJson is List) {
return appsJson
.whereType<Map<String, dynamic>>()
.map((e) => AppInfo.fromJson(e))
.toList();
}
} catch (e) {
_logger.e('解析渠道列表失败: $e');
}
return [];
}
/// 构建渠道索引
Map<UserChannel, AppInfo> _buildChannelIndex(List<AppInfo> apps) {
final index = <UserChannel, AppInfo>{};
for (final app in apps) {
index[app.channel] = app;
}
return index;
}
/// 获取渠道信息,带备选策略
///
/// 优先返回主渠道的信息,如果主渠道不存在则返回备选渠道的信息。
/// 主要用于:
/// 1. 版本更新:优先使用设备品牌渠道,备选使用环境变量渠道
/// 2. 功能配置:优先使用环境变量渠道,备选使用版本更新渠道
AppInfo? _getChannelAppInfo(
Map<UserChannel, AppInfo> index,
UserChannel primary,
UserChannel? fallback,
) {
return index[primary] ?? (fallback != null ? index[fallback] : null);
}
/// 解析版本号字符串为数字数组
List<int> _parseVersion(String version) {
final parts = version.split('.');

132
lib/modules/usage/controllers/meeting_stats_controller.dart

@ -17,23 +17,49 @@ class MeetingStatsController extends GetxController {
allAudioFileBytes: 0,
allTranscriptBytes: 0,
).obs;
final Rx<MeetingAsrHistoryStats> weekStats = MeetingAsrHistoryStats(
final Rx<MeetingAsrHistoryStats> historyStats = MeetingAsrHistoryStats(
userId: '',
allAsrCalls: 0,
allAudioSeconds: 0,
allAudioFileBytes: 0,
allTranscriptBytes: 0,
).obs;
final Rx<MeetingAsrHistoryStats> monthStats = MeetingAsrHistoryStats(
final RxList<MeetingAsrDailyStats> historyDaily =
<MeetingAsrDailyStats>[].obs;
final Rx<MeetingStorageHistoryStats> todayStorageStats =
MeetingStorageHistoryStats(
userId: '',
allStorageUploads: 0,
allStorageBytes: 0,
allTransferBytes: 0,
).obs;
final Rx<MeetingStorageHistoryStats> historyStorageStats =
MeetingStorageHistoryStats(
userId: '',
allStorageUploads: 0,
allStorageBytes: 0,
allTransferBytes: 0,
).obs;
final Rx<DateTime?> rangeStart = Rx<DateTime?>(null);
final Rx<DateTime?> rangeEnd = Rx<DateTime?>(null);
final Rx<MeetingAsrHistoryStats> rangeStats = MeetingAsrHistoryStats(
userId: '',
allAsrCalls: 0,
allAudioSeconds: 0,
allAudioFileBytes: 0,
allTranscriptBytes: 0,
).obs;
final RxList<MeetingAsrDailyStats> historyDaily =
<MeetingAsrDailyStats>[].obs;
final Rx<MeetingStorageHistoryStats> 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<void> refreshData() async {
await loadAllStats();
}
Future<void> 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';
}
}

145
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<MeetingStatsController> {
const MeetingStatsView({Key? key}) : super(key: key);
@ -14,14 +15,14 @@ class MeetingStatsView extends GetView<MeetingStatsController> {
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<MeetingStatsController> {
),
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<MeetingStatsController> {
}
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<MeetingStatsController> {
});
}
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<MeetingStatsController> {
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<MeetingStatsController> {
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<MeetingStatsController> {
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)),
],
),
],
),
);

25
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 = ""
}
}
}

Loading…
Cancel
Save