diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 890b9e1a7..dda0379b0 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,6 +1,10 @@
PODS:
- amap_service (0.0.1):
+ - AMapNavi (~> 10.0.900)
- Flutter
+ - AMapFoundation (1.8.2)
+ - AMapNavi (10.0.900):
+ - AMapFoundation (>= 1.8.2)
- audio_waveforms (0.0.1):
- Flutter
- classic_bluetooth (0.0.1):
@@ -102,6 +106,8 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
+ - AMapFoundation
+ - AMapNavi
- FBAEMKit
- FBSDKCoreKit
- FBSDKCoreKit_Basics
@@ -152,7 +158,9 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/tencentcloud_cos_sdk_plugin/ios"
SPEC CHECKSUMS:
- amap_service: 3110d959589c6d49570ebe9162418d7b046df30d
+ amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b
+ AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec
+ AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
classic_bluetooth: c146e1618e867a47fa7ab79a3bf262e7d69d557d
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index b7e7fc98f..bc3f4a793 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -76,10 +76,6 @@
EFAB
- NSAccessorySetupKitSupports
-
- Bluetooth
-
NSAccessorySetupDescriptions
@@ -89,7 +85,10 @@
语音助理耳机
-
+ NSAccessorySetupKitSupports
+
+ Bluetooth
+
NSAppTransportSecurity
NSAllowsArbitraryLoads
@@ -98,7 +97,7 @@
NSExceptionDomains
- mcp.ideapsound.com
+ mcp.amap.com
NSExceptionAllowsInsecureHTTPLoads
@@ -107,7 +106,7 @@
NSIncludesSubdomains
- mcp.amap.com
+ mcp.ideapsound.com
NSExceptionAllowsInsecureHTTPLoads
@@ -122,24 +121,27 @@
需要蓝牙权限用于连接和管理音频设备
NSBluetoothPeripheralUsageDescription
需要蓝牙权限用于连接和管理音频设备
+ NSCalendarsUsageDescription
+ 需要日历权限用于安排和管理您的音频会议和录音任务
+ NSCameraUsageDescription
+ 需要相机权限用于扫描二维码和视频通话功能
NSLocationAlwaysAndWhenInUseUsageDescription
语音助手需要位置权限来为您提供基于位置的信息和服务
NSLocationWhenInUseUsageDescription
语音助手需要位置权限来为您提供基于位置的信息和服务
NSMicrophoneUsageDescription
需要麦克风权限用于语音识别和录音功能
- NSCalendarsUsageDescription
- 需要日历权限用于安排和管理您的音频会议和录音任务
+ NSMotionUsageDescription
+ 我们需要访问您的运动数据以提供更精准的导航服务
NSPhotoLibraryUsageDescription
需要相册权限用于保存录音文件和导入音频文件
- NSCameraUsageDescription
- 需要相机权限用于扫描二维码和视频通话功能
UIApplicationSupportsIndirectInputEvents
UIBackgroundModes
bluetooth-central
audio
+ location
UILaunchStoryboardName
LaunchScreen
diff --git a/lib/data/models/appconfig_model.dart b/lib/data/models/appconfig_model.dart
index b95b5fae2..602abf4dd 100644
--- a/lib/data/models/appconfig_model.dart
+++ b/lib/data/models/appconfig_model.dart
@@ -52,7 +52,7 @@ class DBAgent {
class DBMCPServer {
final String servername;
final String url;
- final String? tools;
+ final String tools;
DBMCPServer({
required this.servername,
required this.url,
diff --git a/lib/data/models/appconfig_model.g.dart b/lib/data/models/appconfig_model.g.dart
index 6d47874e6..c4ac4cc90 100644
--- a/lib/data/models/appconfig_model.g.dart
+++ b/lib/data/models/appconfig_model.g.dart
@@ -57,7 +57,7 @@ Map _$DBAgentToJson(DBAgent instance) => {
DBMCPServer _$DBMCPServerFromJson(Map json) => DBMCPServer(
servername: json['servername'] as String,
url: json['url'] as String,
- tools: json['tools'] as String?,
+ tools: json['tools'] as String? ?? '',
);
Map _$DBMCPServerToJson(DBMCPServer instance) =>
diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index 93960f0d8..f1a51375c 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -4,7 +4,6 @@ import 'dart:io';
import 'package:amap_service/amap_service.dart';
import 'package:deep_voice/data/services/music_manager.dart';
import 'package:deep_voice/data/services/navigation_manager.dart';
-import 'package:deep_voice/routes/app_routes.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:agent_service/agent_service.dart';
@@ -12,32 +11,11 @@ import 'package:chat_storage/chat_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'package:get_storage/get_storage.dart';
import 'package:permission_handler/permission_handler.dart';
-import 'package:app_settings/app_settings.dart';
import 'package:deep_voice/core/utils/logger.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photo_view/photo_view.dart';
import '../../../core/utils/permission_util.dart';
-
-class Message {
- final bool isUser; //标识信息是否来自用户还是AI
- final String text; //信息文本内容
- final DateTime timestamp; // 消息时间戳
- final bool isRecognizing; // 是否为语音识别中的临时消息
- final bool hasImage; // 是否包含图片
- final String? imagePath; // 图片路径
- final Map? meta; //卡片数据
-
- Message({
- // 构造函数
- required this.isUser,
- required this.text,
- DateTime? timestamp,
- this.isRecognizing = false,
- this.hasImage = false,
- this.imagePath,
- this.meta,
- }) : this.timestamp = timestamp ?? DateTime.now();
-}
+import '../../../data/models/message_model.dart';
class AgentController extends GetxController {
static AgentController get to => Get.find(); //单例访问模式
@@ -214,7 +192,7 @@ class AgentController extends GetxController {
}
}
- // 添加欢迎消息
+ // 添加初始欢迎消息
void _addWelcomeMessage() {
messages.add(Message(
isUser: false,
@@ -418,7 +396,7 @@ class AgentController extends GetxController {
break;
case AgentServiceEventType.functionCallResult:
- final responseId = event.data['responseId'] ?? '';
+ final responseId = event.data['responseId'] ?? '';
// final result = event.data['result'];
final metaStr = event.data['meta'] ?? '';
// final functionCall = event.data['function_call'] ?? '';
@@ -521,8 +499,10 @@ class AgentController extends GetxController {
}
// 发送消息(包括所有支持的消息类型)
- Future sendTextMessage() async {
- // 如果图片输入是活跃的,使用选中的图片和文本框的内容
+ //场景1:用户在文本输入框中输入文本,点击发送按钮,调用sendMessage()方法。
+ //场景2:当用户选择图片后在文本框输入内容并发送时,调用sendMessage()转发给sendImageMessage()。
+ Future sendMessage() async {
+ // 1. 处理图片+文本的混合输入
if (isImageInputActive.value && selectedImagePath.value != null) {
final text = textController.text.trim();
final imagePath = selectedImagePath.value!; // 使用!强制断言非空
@@ -533,6 +513,7 @@ class AgentController extends GetxController {
return;
}
+ // 2. 处理纯文本消息
final text = textController.text.trim();
if (text.isEmpty) return;
@@ -541,7 +522,7 @@ class AgentController extends GetxController {
textController.clear();
// 使用reverse:true时通常不需要手动滚动
-
+ // 3. 调用AI服务处理
try {
isProcessing.value = true;
// 标记为新的AI回复
@@ -636,36 +617,6 @@ class AgentController extends GetxController {
}
}
- // 保存消息到持久化存储
- // Future _saveMessageToStorage(Message message) async {
- // try {
- // // 准备元数据,包含图片信息
- // Map metadata = {};
- // if (message.meta != null) {
- // metadata = Map.from(message.meta!);
- // }
-
- // // 添加图片相关信息到元数据
- // if (message.hasImage) {
- // metadata['hasImage'] = true;
- // metadata['imagePath'] = message.imagePath;
- // }
-
- // // 保存消息
- // await ChatStorage.saveMessage(
- // sessionId: sessionId,
- // sender: message.isUser ? 'user' : 'assistant',
- // message: message.text,
- // metadata: metadata.isNotEmpty ? jsonEncode(metadata) : null,
- // //timestamp: message.timestamp.toIso8601String(),
- // );
-
- // Logger.i(TAG, '消息已保存到持久化存储');
- // } catch (e) {
- // Logger.e(TAG, '保存消息到持久化存储失败: $e');
- // }
- // }
-
// 切换输入模式
void toggleInputMode() async {
// 如果当前在图片输入模式,先清除
@@ -703,156 +654,6 @@ class AgentController extends GetxController {
}
}
-// 检查麦克风权限,返回true表示已授权,false表示未授权
- Future _checkMicrophonePermission() async {
- try {
- // 1.先获取当前麦克风权限状态
- final micStatus = await Permission.microphone.status;
-
- // 2.如果麦克风权限已授予,返回true
- if (micStatus.isGranted) {
- return true;
- }
-
- // 3.创建待请求权限列表(这里只请求麦克风权限)
- List permissionsToRequest = [];
- permissionsToRequest.add(Permission.microphone);
-
- // 4.弹出权限说明对话框(首次拒绝/还没授权时展示)
- if (micStatus.isPermanentlyDenied) {
- _showPermissionDialog(true);
- } else {
- _showPermissionDialog(false);
- }
-
- // 5.请求权限,并获取返回的权限状态集合
- final statuses = await permissionsToRequest.request();
-
- // 6.用于记录被拒绝的权限(便于后续判断)
- Permission? deniedPermission;
-
- // 7.遍历权限状态集合,检查是否有任何权限被拒绝
- statuses.forEach((permission, status) {
- if (!status.isGranted) {
- deniedPermission = permission;
- Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status');
- }
- });
-
- // // 8. 如果是“永久拒绝”(用户勾选了“不再询问”)
- // if (micStatus.isPermanentlyDenied) {
- // _showPermissionDialog(true);
- // return false;
- // }
-
- // // 9. 普通的拒绝情况,再次弹出说明对话框
- // if (deniedPermission != null) {
- // _showPermissionDialog(false);
- // }
-
- // 10. 如果权限未被授予,返回 false
- return false;
- } catch (e) {
- Logger.e(TAG, '检查麦克风权限失败: $e');
- return false;
- }
- }
-
- // 显示权限提示对话框
- void _showPermissionDialog(bool isPermanent) {
- Get.generalDialog(
- barrierDismissible: false,
- barrierLabel: '权限提示',
- transitionBuilder: (context, anim1, anim2, child) {
- return SlideTransition(
- position: Tween(
- begin: Offset(0, -1), // 从上方滑入
- end: Offset(0, 0),
- ).animate(CurvedAnimation(parent: anim1, curve: Curves.easeOut)),
- child: child,
- );
- },
- pageBuilder: (context, anim1, anim2) {
- return SafeArea(
- child: Align(
- alignment: Alignment.topCenter, // 关键:设置为顶部对齐
- child: Padding(
- padding: const EdgeInsets.only(top: 80, left: 24, right: 24),
- child: Material(
- borderRadius: BorderRadius.circular(12),
- color: Colors.white,
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: ConstrainedBox(
- constraints: BoxConstraints(maxWidth: 400),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Text('需要麦克风权限',
- style: TextStyle(
- fontSize: 18, fontWeight: FontWeight.bold)),
- const SizedBox(height: 12),
- Text(isPermanent
- ? '语音功能需要麦克风权限。请前往设置页面手动开启权限。'
- : '语音功能需要麦克风权限。请允许使用麦克风权限以继续使用语音功能。'),
- const SizedBox(height: 16),
- const Text('权限使用说明:',
- style: TextStyle(fontWeight: FontWeight.bold)),
- const SizedBox(height: 8),
- const Text('• 麦克风权限:用于录制您的语音,实现语音翻译、语音助手对话等核心功能'),
- const SizedBox(height: 8),
- const Text('• 我们仅在您主动使用语音功能时访问麦克风'),
- const SizedBox(height: 8),
- const Text('• 您可以随时在系统设置中关闭权限'),
- const SizedBox(height: 20),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- TextButton(
- onPressed: () => Get.back(),
- child: const Text('取消'),
- ),
- const SizedBox(width: 8),
- TextButton(
- onPressed: () {
- Get.back();
- if (isPermanent) {
- AppSettings.openAppSettings();
- } else {
- Permission.microphone.request();
- }
- },
- child: Text(isPermanent ? '前往设置' : '授权'),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- );
- },
- );
- }
-
- // 获取Android SDK版本
- Future _getAndroidSdkVersion() async {
- if (Platform.isAndroid) {
- try {
- // 这里我们简单返回一个较高的值,确保请求所有可能需要的权限
- // 在实际应用中,你可能需要使用platform channel获取真实的SDK版本
- return 31; // 对应Android 12
- } catch (e) {
- Logger.e(TAG, '获取Android SDK版本失败: $e');
- }
- }
- return 0;
- }
-
// 开始语音输入
Future startVoiceInput() async {
if (isListening.value) return;
diff --git a/lib/modules/agent/controllers/report_controller.dart b/lib/modules/agent/controllers/report_controller.dart
index 0bae0106a..816d8277d 100644
--- a/lib/modules/agent/controllers/report_controller.dart
+++ b/lib/modules/agent/controllers/report_controller.dart
@@ -1,7 +1,8 @@
import 'package:get/get.dart';
-import 'agent_controller.dart'; // 确保导入Message模型
+//import 'agent_controller.dart'; // 确保导入Message模型
import 'package:flutter/material.dart';
import '../../../data/services/network/api.dart';
+import '../../../data/models/message_model.dart';
class ReportController extends GetxController {
// 当前举报的消息
diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart
index 15044c63c..6ba38309e 100644
--- a/lib/modules/agent/views/agent_view.dart
+++ b/lib/modules/agent/views/agent_view.dart
@@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../core/theme/app_colors.dart';
import 'package:haptic_feedback/haptic_feedback.dart';
+import '../../../data/models/message_model.dart';
class AgentView extends StatelessWidget {
const AgentView({Key? key}) : super(key: key);
@@ -384,12 +385,12 @@ class AgentView extends StatelessWidget {
vertical: isImageActive ? 12 : 0),
),
onSubmitted: (_) =>
- controller.sendTextMessage(),
+ controller.sendMessage(),
);
}),
),
GestureDetector(
- onTap: () => controller.sendTextMessage(),
+ onTap: () => controller.sendMessage(),
child: Container(
padding: const EdgeInsets.all(6),
child: Icon(
diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart
index ee6af73c5..c72c22ee5 100644
--- a/lib/modules/agent/views/message_bubble.dart
+++ b/lib/modules/agent/views/message_bubble.dart
@@ -249,12 +249,12 @@ class MessageBubble extends GetView {
rawList.map((e) => Map.from(e)).toList();
//音乐播放列表
return buildMusicContent(context, playlist);
- } else if (meta!.keys.contains('card_weather')) {
- final city = meta!['card_weather']['city'] as String;
- final rawList = meta!['card_weather']['dayweather'] as List;
+ } else if (meta!.keys.contains('maps_weather')) {
+ final city = meta!['maps_weather']['city'] as String;
+ final rawList = meta!['maps_weather']['forecasts'] as List;
final daylist =
rawList.map((e) => Map.from(e)).toList();
- final now = meta!['card_weather']['day'] as Map;
+ final now = daylist[0] as Map;
//天气
return buildWeatherContent(
context,
diff --git a/lib/modules/agent/views/weather_card.dart b/lib/modules/agent/views/weather_card.dart
index aba39038e..323a4a483 100644
--- a/lib/modules/agent/views/weather_card.dart
+++ b/lib/modules/agent/views/weather_card.dart
@@ -62,7 +62,7 @@ Widget buildWeatherContent(BuildContext context, String city,
),
SizedBox(height: 4),
Text(
- now["weatherCondition"],
+ now["dayweather"],
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.9),
@@ -72,7 +72,7 @@ Widget buildWeatherContent(BuildContext context, String city,
Row(
children: [
Text(
- "${now["temperature"]}°",
+ "${now["daytemp"]}°",
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
@@ -84,14 +84,14 @@ Widget buildWeatherContent(BuildContext context, String city,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- '最高: ${now["maxTemperature"]}°',
+ '最高: ${now["daytemp"]}°',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
Text(
- '最低: ${now["minTemperature"]}°',
+ '最低: ${now["nighttemp"]}°',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
@@ -103,8 +103,8 @@ Widget buildWeatherContent(BuildContext context, String city,
),
],
),
- const Icon(
- Icons.wb_sunny,
+ Icon(
+ getWeatherIcon(now['dayweather']),
size: 48,
color: Colors.white,
),
@@ -133,10 +133,10 @@ Widget buildWeatherContent(BuildContext context, String city,
String dateStr = days[index]['date'];
DateTime? date = DateUtil.getDateTime(dateStr);
IconData weatherIcon =
- getWeatherIcon(days[index]['weatherCondition']);
+ getWeatherIcon(days[index]['dayweather']);
String weekday = DateUtil.getWeekday(date,
languageCode: "zh", short: true);
- String temp = "${days[index]['temperature']}°";
+ String temp = "${days[index]['daytemp']}°";
return _buildForecastDay(
weekday,
weatherIcon,
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
index 2c7debbe5..35a858a4f 100644
--- a/lib/modules/pairing/controllers/pairing_controller.dart
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -36,6 +36,10 @@ class PairingController extends GetxController {
final _state = PairingState.init.obs;
PairingState get state => _state.value;
+ // ==================== 手动模式控制 ====================
+ final isManualMode = true.obs;
+ final isReadyToStart = false.obs;
+
// ==================== 状态数据 ====================
final statusDescription = ''.obs;
final actionButtonText = ''.obs;
@@ -78,12 +82,32 @@ class PairingController extends GetxController {
_transition(PairingState.init);
}
+ /// 手动开始配对
+ Future startManualPairing() async {
+ if (!isReadyToStart.value) return;
+
+ Logger.i(_tag, '用户手动启动配对流程');
+ isManualMode.value = false;
+ isReadyToStart.value = false;
+
+ // 开始执行配对流程
+ await _executeInitFlow();
+ }
+
/// 重新开始配对流程
Future restart() async {
_cancelAllOperations();
_transition(PairingState.init);
}
+ /// 重置到手动模式
+ void resetToManualMode() {
+ _cancelAllOperations();
+ isManualMode.value = true;
+ isReadyToStart.value = true;
+ _transition(PairingState.init);
+ }
+
/// 处理按钮点击
void handleActionButtonTap() {
switch (state) {
@@ -174,10 +198,24 @@ class PairingController extends GetxController {
// ==================== 状态处理方法 ====================
- /// INIT: 初始化,检查权限
+ /// INIT: 初始化状态
Future _enterInit() async {
- Logger.i(_tag, '开始执行初始化流程');
+ Logger.i(_tag, '进入初始化状态');
+ if (isManualMode.value) {
+ // 手动模式:仅显示待机状态,不执行任何操作
+ isLoading.value = false;
+ statusDescription.value = '准备就绪';
+ isReadyToStart.value = true;
+ return;
+ }
+
+ // 自动模式:执行原有的初始化逻辑
+ await _executeInitFlow();
+ }
+
+ /// 执行初始化流程
+ Future _executeInitFlow() async {
isLoading.value = true;
statusDescription.value = '正在初始化...';
@@ -529,12 +567,6 @@ class PairingController extends GetxController {
}
// Android权限检查 - 使用 PermissionUtil
- // final context = Get.context;
- // if (context == null) {
- // Logger.e(_tag, '无法获取有效上下文,跳过权限请求');
- // return false;
- // }
-
// 根据 Android 版本确定需要的权限
List requiredPermissions = [
Permission.locationWhenInUse,
@@ -551,7 +583,6 @@ class PairingController extends GetxController {
Logger.d(_tag, '正在请求 $permissionName 权限');
final granted = await PermissionUtil.instance.requestPermission(
- // context: context,
permissionType: permission,
permissionName: permissionName,
explanationText: _getExplanationText(permission),
@@ -646,7 +677,11 @@ class PairingController extends GetxController {
// 根据状态更新UI文本
switch (state) {
case PairingState.init:
- statusDescription.value = '初始化中...';
+ if (isManualMode.value && isReadyToStart.value) {
+ statusDescription.value = '准备就绪';
+ } else {
+ statusDescription.value = '初始化中...';
+ }
break;
case PairingState.btReady:
statusDescription.value = '蓝牙已就绪';
@@ -718,7 +753,8 @@ class PairingController extends GetxController {
/// 是否可以跳过配对
bool get canSkip =>
!isLoading.value &&
- (state == PairingState.exitNoClassic ||
+ ((isManualMode.value && isReadyToStart.value) || // 手动模式待机状态可以跳过
+ state == PairingState.exitNoClassic ||
state == PairingState.exitNotMatch ||
state == PairingState.exitTimeout);
diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart
index f15cf8f34..abc48189e 100644
--- a/lib/modules/pairing/views/pairing_view.dart
+++ b/lib/modules/pairing/views/pairing_view.dart
@@ -99,11 +99,13 @@ class PairingView extends GetView {
return Obx(() {
final state = controller.state;
final isLoading = controller.isLoading.value;
-
+ final isReadyToStart = controller.isReadyToStart.value;
+ final isManualMode = controller.isManualMode.value;
+
// 根据状态决定动画颜色
Color primaryColor = Color(0xFF0066CC);
Color secondaryColor = Color(0xFF5E5CE6);
-
+
// 错误状态使用红色
if (state.name.startsWith('exit')) {
primaryColor = Colors.red;
@@ -114,7 +116,54 @@ class PairingView extends GetView {
primaryColor = Colors.green;
secondaryColor = Colors.greenAccent;
}
-
+
+ // 手动模式待机状态的特殊处理
+ if (isManualMode && isReadyToStart) {
+ return Container(
+ width: 240.w,
+ height: 240.w,
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ // 静态背景圆圈
+ Container(
+ width: 200.w,
+ height: 200.w,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ primaryColor.withOpacity(0.1),
+ secondaryColor.withOpacity(0.1),
+ ],
+ ),
+ shape: BoxShape.circle,
+ ),
+ ),
+ // 设备图标
+ Container(
+ width: 120.w,
+ height: 120.w,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [primaryColor, secondaryColor],
+ ),
+ shape: BoxShape.circle,
+ ),
+ child: Icon(
+ Icons.headphones,
+ color: Colors.white,
+ size: 60.w,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
return Container(
width: 240.w,
height: 240.w,
@@ -239,11 +288,11 @@ class PairingView extends GetView {
),
// 当前连接设备(有设备时才显示)
- if (device != null &&
- (state == PairingState.classicOk ||
- state == PairingState.serverVerify ||
- state == PairingState.pairing ||
- state == PairingState.paired)) ...[
+ if (device != null &&
+ (state == PairingState.classicOk ||
+ state == PairingState.serverVerify ||
+ state == PairingState.pairing ||
+ state == PairingState.paired)) ...[
SizedBox(height: 24.h),
_buildConnectedDevice(device, isDarkMode),
],
@@ -254,10 +303,14 @@ class PairingView extends GetView {
// 根据状态获取副标题
String _getSubtitleForState(PairingState state) {
+ if (controller.isManualMode.value && controller.isReadyToStart.value) {
+ return '点击下方按钮开始配对您的DeapSound设备';
+ }
+
switch (state) {
case PairingState.init:
case PairingState.btReady:
- return '正在准备配对环境...';
+ return controller.isManualMode.value ? '准备开始配对' : '正在准备配对环境...';
case PairingState.classicOk:
return '请先通过系统蓝牙连接DeapSound设备';
case PairingState.serverVerify:
@@ -364,17 +417,50 @@ class PairingView extends GetView {
final showActionButton = controller.showActionButton;
final canSkip = controller.canSkip;
final actionButtonText = controller.actionButtonText.value;
-
+ final isReadyToStart = controller.isReadyToStart.value;
+ final isManualMode = controller.isManualMode.value;
+
return Container(
width: double.infinity,
child: Column(
children: [
- // 主按钮 - 根据状态显示不同的按钮
- if (showActionButton)
+ // 手动模式:显示开始配对按钮
+ if (isManualMode && isReadyToStart)
+ ElevatedButton(
+ onPressed: () => controller.startManualPairing(),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Color(0xFF0066CC),
+ foregroundColor: Colors.white,
+ padding: EdgeInsets.symmetric(vertical: 16.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(16.r),
+ ),
+ elevation: 0,
+ minimumSize: Size(double.infinity, 56.h),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.bluetooth_searching, size: 20.w),
+ SizedBox(width: 8.w),
+ Text(
+ '开始配对设备',
+ style: TextStyle(
+ fontSize: 16.sp,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ ),
+
+ // 原有的状态按钮(错误处理等)
+ if (showActionButton && !isManualMode)
ElevatedButton(
onPressed: () => controller.handleActionButtonTap(),
style: ElevatedButton.styleFrom(
- backgroundColor: _getButtonColor(controller.state, isDarkMode),
+ backgroundColor:
+ _getButtonColor(controller.state, isDarkMode),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
@@ -392,7 +478,7 @@ class PairingView extends GetView {
),
),
- // 只在可以跳过时显示跳过按钮
+ // 跳过按钮和重置按钮
if (canSkip) ...[
SizedBox(height: 12.h),
TextButton(
@@ -411,6 +497,24 @@ class PairingView extends GetView {
),
),
],
+
+ // 添加重置按钮(在错误状态下)
+ if (!isManualMode &&
+ !controller.isLoading.value &&
+ controller.state.name.startsWith('exit')) ...[
+ SizedBox(height: 8.h),
+ TextButton(
+ onPressed: () => controller.resetToManualMode(),
+ style: TextButton.styleFrom(
+ foregroundColor: Colors.grey,
+ padding: EdgeInsets.symmetric(vertical: 8.h),
+ ),
+ child: Text(
+ '重新配对',
+ style: TextStyle(fontSize: 14.sp),
+ ),
+ ),
+ ],
],
),
);
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 90e318861..c3854b568 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
@@ -673,7 +673,7 @@ object AgentService : CoroutineScope {
override fun onComplete() {
// 视情况决定是否朗读回复
- if (speakResponse) {
+ if (speakResponse && !nobroadcast) {
ttsService?.flushStream()
}
val response = responseBuilder.toString()
@@ -731,13 +731,16 @@ object AgentService : CoroutineScope {
override fun onFunctionCallResult(functionCall: JSONObject, functionCallResult: JSONObject) {
audioPlayer?.stopAudio()
+ val name = functionCall.get("name") as String;
+ val (metestr, broadcast)= autoHandleFcunCallResult(name,functionCallResult);
+ aiMetadata = metestr;
+ nobroadcast = broadcast
sendEvent("function_call_result", mapOf(
"function_call" to functionCall.toString(),
"result" to functionCallResult.toString(),
+ "meta" to metestr,
))
- val (metestr, broadcast)= autoHandleFcunCallResult(functionCallResult);
- aiMetadata = metestr;
- nobroadcast = broadcast
+
}
}
)
@@ -1102,55 +1105,86 @@ object AgentService : CoroutineScope {
}
}
}
- /*
+
+ /*
* 自动播放音乐
* */
- fun autoHandleFcunCallResult(functionCallResult: JSONObject): Pair{
- val metaStr = functionCallResult.optString("meta")
- var broadcast = false;
- if (metaStr.isNotEmpty()) {
- val meta = JSONObject(metaStr)
- // Log.d(TAG, "mcp调用结果: $metaStr")
- if (meta.has("card_music")) { //音乐卡片
- val cardMusic = meta.getJSONObject("card_music")
- val id = cardMusic.optString("id", "")
- val url = cardMusic.optString("url", "")
- val name = cardMusic.optString("name", "")
- val sgener = cardMusic.optString("sgener", "")
- val image = cardMusic.optString("image", "")
- processMusicPlay(mapOf("id" to id,"url" to url, "title" to name, "artist" to sgener,"coverUrl" to image))
- broadcast = true;
- }else if (meta.has("card_musiclist")) { //音乐列表
- val cardMusiclist = meta.getJSONObject("card_musiclist")
- val musics = cardMusiclist.optJSONArray("musics")
- if (musics != null) {
- val playlist = mutableListOf