Browse Source

feat(语音交互): 新增语言打断功能并同步状态

添加语言打断功能开关,包括:
1. 在三个语言文件中新增翻译键
2. 修复存储键名拼写错误(interrupe->interrupt)
3. 在Home和Agent页面添加开关控件并保持状态同步
4. 通过监听存储实现跨页面状态更新
weicu
lxm 12 months ago
parent
commit
6fbb164940
  1. 1
      lib/core/translations/language/en_us.dart
  2. 3
      lib/core/translations/language/zh_cn.dart
  3. 2
      lib/core/translations/language/zh_hk.dart
  4. 4
      lib/data/services/ble_manager.dart
  5. 18
      lib/modules/agent/controllers/agent_controller.dart
  6. 10
      lib/modules/agent/views/agent_view.dart
  7. 55
      lib/modules/home/controllers/home_controller.dart
  8. 94
      lib/modules/home/views/home_view.dart

1
lib/core/translations/language/en_us.dart

@ -543,6 +543,7 @@ const Map<String, String> enUS = {
"loadFailedClickRetry": "Load failed, click to retry", "loadFailedClickRetry": "Load failed, click to retry",
"swipeUpForMore": "Swipe up for more", "swipeUpForMore": "Swipe up for more",
"callingTool": "I'm helping you, please wait!", // 正在帮您,请稍等! "callingTool": "I'm helping you, please wait!", // 正在帮您,请稍等!
"language_interrupt": "Language Interrupt", // 语言打断
// 预设提示词 // 预设提示词
"presetMusic": "Play some music", // Play some music "presetMusic": "Play some music", // Play some music

3
lib/core/translations/language/zh_cn.dart

@ -513,7 +513,8 @@ const Map<String, String> zhCN = {
"loadMoreMessages": "加载更多消息...", "loadMoreMessages": "加载更多消息...",
"loadFailedClickRetry": "加载失败,点击重试", "loadFailedClickRetry": "加载失败,点击重试",
"swipeUpForMore": "向上滑动查看更多", "swipeUpForMore": "向上滑动查看更多",
"callingTool": "正在帮您,请稍等!", "callingTool": "正在帮您,请稍等!", // 正在帮您,请稍等!
"language_interrupt": "语言打断", // 语言打断
// 预设提示词 // 预设提示词
"presetMusic": "来点音乐", // 来点音乐 "presetMusic": "来点音乐", // 来点音乐

2
lib/core/translations/language/zh_hk.dart

@ -295,6 +295,8 @@ const Map<String, String> zhHK = {
"loadMoreMessages": "載入更多訊息...", "loadMoreMessages": "載入更多訊息...",
"loadFailedClickRetry": "載入失敗,點擊重試", "loadFailedClickRetry": "載入失敗,點擊重試",
"swipeUpForMore": "向上滑動查看更多", "swipeUpForMore": "向上滑動查看更多",
"callingTool": "正在帮您,请稍等!", // 正在帮您,请稍等!
"language_interrupt": "语言打断", // 语言打断
// Agent controller // Agent controller
'welcomeMessage': '你好!我是**小言**,我能幫你:\n\n' 'welcomeMessage': '你好!我是**小言**,我能幫你:\n\n'

4
lib/data/services/ble_manager.dart

@ -650,7 +650,7 @@ class BleManager extends GetxService {
// 加载TTS偏好设置 // 加载TTS偏好设置
Future<bool> _loadTtsInterrupe() async { Future<bool> _loadTtsInterrupe() async {
try { try {
if (!_storage.hasData("agent_tts_interrupe")) { if (!_storage.hasData("agent_tts_interrupt")) {
// 获取设备类型以确定检查策略 // 获取设备类型以确定检查策略
String deviceName = getDeviceName(); String deviceName = getDeviceName();
DBProduct? product = AppConfig.getproduct(deviceName); DBProduct? product = AppConfig.getproduct(deviceName);
@ -662,7 +662,7 @@ class BleManager extends GetxService {
return false; return false;
} }
} else { } else {
return _storage.read('agent_tts_interrupe') ?? false; return _storage.read('agent_tts_interrupt') ?? false;
} }
} catch (e) { } catch (e) {
return false; return false;

18
lib/modules/agent/controllers/agent_controller.dart

@ -168,6 +168,18 @@ class AgentController extends GetxController with WidgetsBindingObserver {
_initAppDir(); _initAppDir();
_loadTtsPreference(); //加载TTS偏好设置 _loadTtsPreference(); //加载TTS偏好设置
_loadTtsInterrupe(); //加载TTS播报打断偏好设置 _loadTtsInterrupe(); //加载TTS播报打断偏好设置
// 监听存储中的“agent_tts_interrupe”键变化,实现跨页面状态同步
// 中文注释:当 Home 页或 Agent 页任意一处切换时,另一处可以通过监听存储自动刷新 UI
_storage.listenKey('agent_tts_interrupt', (value) {
try {
final bool newValue = value is bool ? value : (value == 'true');
if (isTtsInterruptEnabled.value != newValue) {
isTtsInterruptEnabled.value = newValue;
}
} catch (e) {
// 中文注释:忽略异常,避免因类型不一致导致崩溃
}
});
_setupScrollListener(); //设置滚动监听器 _setupScrollListener(); //设置滚动监听器
_loadChatHistory(); //加载聊天历史记录 _loadChatHistory(); //加载聊天历史记录
_subscribeToEvents(); //订阅代理服务事件 _subscribeToEvents(); //订阅代理服务事件
@ -266,7 +278,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
// 加载TTS偏好设置 // 加载TTS偏好设置
Future<void> _loadTtsInterrupe() async { Future<void> _loadTtsInterrupe() async {
try { try {
if (!_storage.hasData("agent_tts_interrupe")) { if (!_storage.hasData("agent_tts_interrupt")) {
// 获取设备类型以确定检查策略 // 获取设备类型以确定检查策略
String deviceName = getDeviceName(); String deviceName = getDeviceName();
DBProduct? product = AppConfig.getproduct(deviceName); DBProduct? product = AppConfig.getproduct(deviceName);
@ -279,7 +291,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
} }
} else { } else {
isTtsInterruptEnabled.value = isTtsInterruptEnabled.value =
_storage.read('agent_tts_interrupe') ?? false; _storage.read('agent_tts_interrupt') ?? false;
} }
await AgentService.isInterrupt(isTtsInterruptEnabled.value); await AgentService.isInterrupt(isTtsInterruptEnabled.value);
} catch (e) { } catch (e) {
@ -293,7 +305,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
/// 当启用播报打断时,用户说话会自动停止当前的TTS播放 /// 当启用播报打断时,用户说话会自动停止当前的TTS播放
Future<void> toggleTtsInterrupt() async { Future<void> toggleTtsInterrupt() async {
isTtsInterruptEnabled.toggle(); isTtsInterruptEnabled.toggle();
await _storage.write('agent_tts_interrupe', isTtsInterruptEnabled.value); await _storage.write('agent_tts_interrupt', isTtsInterruptEnabled.value);
// 保存偏好设置 // 保存偏好设置
try { try {
await AgentService.isInterrupt(isTtsInterruptEnabled.value); await AgentService.isInterrupt(isTtsInterruptEnabled.value);

10
lib/modules/agent/views/agent_view.dart

@ -176,7 +176,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
), ),
color: isDarkMode ? Colors.grey[800] : Colors.white, color: isDarkMode ? Colors.grey[800] : Colors.white,
itemBuilder: (context) => [ itemBuilder: (context) => [
// 播报打断开关选项 // 显示工具开关选项
PopupMenuItem( PopupMenuItem(
child: Obx(() => Row( child: Obx(() => Row(
children: [ children: [
@ -225,7 +225,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(width: 8.w), SizedBox(width: 8.w),
Text( Text(
//'ttsInterruptToggle'.tr, //'ttsInterruptToggle'.tr,
"播报打断", "language_interrupt".tr, // 语言打断
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
const Spacer(), const Spacer(),
@ -1286,7 +1286,8 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
// 构建加载更多指示器 // 构建加载更多指示器
Widget _buildLoadMoreIndicator() { Widget _buildLoadMoreIndicator() {
return Obx(() { return Obx(
() {
if (controller.isLoadingMore.value) { if (controller.isLoadingMore.value) {
// 正在加载状态 // 正在加载状态
return Container( return Container(
@ -1357,6 +1358,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
), ),
); );
} }
}); },
);
} }
} }

55
lib/modules/home/controllers/home_controller.dart

@ -1,5 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:agent_service/agent_service.dart';
// 移除了dart:async导入,因为不再使用Timer // 移除了dart:async导入,因为不再使用Timer
import '../../../data/services/novice_guide_service.dart'; import '../../../data/services/novice_guide_service.dart';
import '../../../routes/app_routes.dart'; import '../../../routes/app_routes.dart';
@ -32,6 +33,9 @@ class HomeController extends GetxController {
final _isFullyConnected = false.obs; final _isFullyConnected = false.obs;
String? _lastConnectedDeviceName; String? _lastConnectedDeviceName;
// 播报打断开关状态(与 AgentController 对齐)
final isTtsInterruptEnabled = false.obs; // 是否启用播报打断
// 移除了电池信息定时刷新相关变量 // 移除了电池信息定时刷新相关变量
// OTA检查服务 // OTA检查服务
@ -101,6 +105,19 @@ class HomeController extends GetxController {
_otaCheckService = Get.find<OtaCheckService>(); _otaCheckService = Get.find<OtaCheckService>();
_loadLastConnectedDeviceName(); // 加载持久化的设备名称 _loadLastConnectedDeviceName(); // 加载持久化的设备名称
_initWithCachedInfo(); _initWithCachedInfo();
_loadTtsInterrupt(); // 加载播报打断开关初始状态
// 监听存储中的“agent_tts_interrupe”键变化,实现与 Agent 页同步
// 中文注释:当 Agent 页或 Home 页任意一处切换时,另一处通过此监听自动刷新开关状态
_storage.listenKey('agent_tts_interrupt', (value) {
try {
final bool newValue = value is bool ? value : (value == 'true');
if (isTtsInterruptEnabled.value != newValue) {
isTtsInterruptEnabled.value = newValue;
}
} catch (e) {
// 中文注释:忽略异常,保持现有状态,避免因类型转换失败导致崩溃
}
});
// 移除了电池信息定时刷新的启动 // 移除了电池信息定时刷新的启动
} }
@ -144,6 +161,44 @@ class HomeController extends GetxController {
}); });
} }
/// 加载“播报打断”开关状态
/// 逻辑与 AgentController 保持一致:
/// 1)若本地不存在存储,则根据设备产品的 iscontinuouschat 取反作为默认值;
/// 2)若存在存储则直接读取;
/// 3)同步到原生服务 AgentService.isInterrupt。
Future<void> _loadTtsInterrupt() async {
try {
if (!_storage.hasData('agent_tts_interrupt')) {
final deviceName = getDeviceName();
final DBProduct? product = AppConfig.getproduct(deviceName);
if (deviceName.isNotEmpty && product != null) {
isTtsInterruptEnabled.value = !(product.iscontinuouschat ?? true);
} else {
isTtsInterruptEnabled.value = false;
}
} else {
isTtsInterruptEnabled.value =
_storage.read('agent_tts_interrupt') ?? false;
}
await AgentService.isInterrupt(isTtsInterruptEnabled.value);
} catch (e) {
Logger.e(_tag, '加载播报打断开关失败: $e');
isTtsInterruptEnabled.value = false;
}
}
/// 切换“播报打断”开关
/// 同步保存到本地,并通知原生服务。
Future<void> toggleTtsInterrupt() async {
try {
isTtsInterruptEnabled.toggle();
await _storage.write('agent_tts_interrupt', isTtsInterruptEnabled.value);
await AgentService.isInterrupt(isTtsInterruptEnabled.value);
} catch (e) {
Logger.e(_tag, '保存播报打断偏好失败: $e');
}
}
/// 从本地存储加载最后连接的设备名称 /// 从本地存储加载最后连接的设备名称
void _loadLastConnectedDeviceName() { void _loadLastConnectedDeviceName() {
_lastConnectedDeviceName = _storage.read(_lastDeviceNameKey); _lastConnectedDeviceName = _storage.read(_lastDeviceNameKey);

94
lib/modules/home/views/home_view.dart

@ -170,23 +170,89 @@ class HomeView extends GetView<HomeController> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200], color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ // boxShadow: [
BoxShadow( // BoxShadow(
color: Colors.grey.withOpacity(0.2), // color: Colors.grey.withOpacity(0.2),
blurRadius: 4, // blurRadius: 4,
offset: const Offset(0, 2), // offset: const Offset(0, 2),
), // ),
], // ],
), ),
child: IconButton( child: Obx(() => PopupMenuButton(
icon: Icon(Icons.settings_outlined,
size: 16.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600]),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onPressed: () { // 中文注释:缩小弹窗整体宽度,让展开窗口更紧凑
Get.toNamed(Routes.settings); constraints: BoxConstraints(
}, minWidth: 120.w,
maxWidth: 140.w,
),
icon: Icon(
controller.isTtsInterruptEnabled.value
? Icons.voice_over_off
: Icons.voice_over_off_outlined,
size: 12.sp,
color: controller.isTtsInterruptEnabled.value
? Theme.of(Get.context!).colorScheme.primary // 使用主题主色
: (isDarkMode ? Colors.white70 : Colors.grey[600]),
),
elevation: 0, // 中文注释:去除弹窗阴影,实现扁平化
// 中文注释:将弹窗位置整体上移,使菜单与“Voitrans”更为平齐
offset: Offset(0, -10.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.w), // 中文注释:略微减小圆角
side: BorderSide.none, // 中文注释:移除边框线,边缘不明显
),
color: isDarkMode ? Colors.grey[900] : Colors.grey[50],
itemBuilder: (context) => [
PopupMenuItem(
// 中文注释:降低菜单项高度并收紧默认内边距,减少上下留白
height: 15.h,
padding:
EdgeInsets.symmetric(horizontal: 2.w, vertical: 0.h),
child: Obx(() => Row(
mainAxisSize: MainAxisSize.max, // 中文注释:收紧行宽度
mainAxisAlignment:
MainAxisAlignment.spaceBetween, // 让 Switch 靠右
children: [
Icon(
controller.isTtsInterruptEnabled.value
? Icons.voice_over_off
: Icons.voice_over_off_outlined,
color: controller.isTtsInterruptEnabled.value
? Theme.of(context).colorScheme.primary
: (isDarkMode
? Colors.grey[400]
: Colors.grey),
size: 14.sp, // 中文注释:进一步缩小图标尺寸
),
SizedBox(width: 4.w),
Text(
// 翻译键代表的中文文本内容:语言打断
'language_interrupt'.tr, // 语言打断
style: TextStyle(
fontSize: 11.sp,
height: 1.0, // 中文注释:降低行高,减小垂直占用
),
), ),
//SizedBox(width: 0.w), // 中文注释:控制文字与开关间距
Transform.scale(
scale: 0.6, // 中文注释:缩放开关控件,减小视觉尺寸
child: Switch(
value: controller.isTtsInterruptEnabled.value,
onChanged: (value) =>
controller.toggleTtsInterrupt(),
activeColor:
Theme.of(context).colorScheme.primary,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
// 中文注释:删除不支持的 visualDensity 参数,保留缩放缩小尺寸
),
),
],
)),
onTap: () => controller.toggleTtsInterrupt(),
),
],
)),
), ),
], ],
); );

Loading…
Cancel
Save