From 30f1a7c73299ea2bfabffcd4d387b2217fdb1d5f Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sun, 28 Sep 2025 17:41:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/Info.plist | 2 + .../controllers/pairing_controller.dart | 9 +- .../chat_api/SystemFunctionHandler.swift | 151 ++++++++++++++++-- 3 files changed, 147 insertions(+), 15 deletions(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index ec7521c06..7720e0d46 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -157,6 +157,8 @@ Calendar permission is required to schedule and manage your audio meetings and recording tasks NSCameraUsageDescription Camera permission is required for QR code scanning and video calling functions + NSContactsUsageDescription + Contacts access is required to help you make phone calls to your contacts through voice commands. This enables the AI assistant to find and dial your contacts when you ask it to call someone. NSLocationAlwaysAndWhenInUseUsageDescription Location access enables the app to provide location-based voice translation services, such as translating local place names and providing contextual information relevant to your current location during conversations. NSLocationWhenInUseUsageDescription diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 6a53441b2..0fe283f89 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -771,10 +771,10 @@ class PairingController extends GetxController with WidgetsBindingObserver { final deviceName = device.name; DBProduct? product; // 检查是否已有BLE绑定的设备 - /// 检查是否已有BLE绑定的设备 + // 检查是否已有BLE绑定的设备 if (AppConfig.config.products.isNotEmpty) { final match = AppConfig.config.products.where( - (d) => d.devicename == device.name, + (d) => d.devicename.contains(deviceName), ); product = match.isNotEmpty ? match.first : null; } @@ -813,9 +813,8 @@ class PairingController extends GetxController with WidgetsBindingObserver { _bleManager.disconnect(); } } - + // 检查是否已有BLE绑定的设备 if (Platform.isAndroid) { - // 检查是否已有BLE绑定的设备 final existingDevice = User.instance.devices.firstWhere( (d) => d.devicetype == 2 && @@ -838,7 +837,7 @@ class PairingController extends GetxController with WidgetsBindingObserver { } print( '获取到 deviceName: $deviceName address: ${device.address} uuids:$uuids'); - if (names.any((name) => name.contains(deviceName))) { + if (product != null && product.devicetype != 1) { if (currentBleState == 2) { //已连接直接返回 return 9; diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift index 8249559fa..93db410fa 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift @@ -5,7 +5,7 @@ import CoreLocation import MessageUI import location_service import user_portrait // 添加这行 - +import Contacts import MediaPlayer import AVFoundation import UserNotifications @@ -74,6 +74,9 @@ public class SystemFunctionHandler: NSObject { // 注册发送短信函数 registerSendTextMessageFunction(client: client) + // 注册查询通讯录函数 + registerGetContactsFunction(client: client) + // 注册拨打电话函数 registerMakePhoneCallFunction(client: client) @@ -181,23 +184,40 @@ public class SystemFunctionHandler: NSObject { ) } + /// 注册查询通讯录函数 + private func registerGetContactsFunction(client: MCPClient) { + let parameters: [String: Any] = [ + "type": "object", + "properties": [:], + "required": [] + ] + + let handler = GetContactsHandler() + _ = client.registerLocalFunction( + name: "get_contacts", + description: "获取通讯录中的所有联系人", + parameters: parameters, + handler: handler + ) + } + /// 注册拨打电话函数 private func registerMakePhoneCallFunction(client: MCPClient) { let parameters: [String: Any] = [ "type": "object", "properties": [ - "contact": [ + "phone_number": [ "type": "string", - "description": "联系人姓名或电话号码" + "description": "要拨打的电话号码" ] ], - "required": ["contact"] + "required": ["phone_number"] ] let handler = MakePhoneCallHandler() _ = client.registerLocalFunction( name: "make_phone_call", - description: "拨打电话", + description: "拨打指定电话号码", parameters: parameters, handler: handler ) @@ -661,16 +681,22 @@ private class SendTextMessageHandler: NSObject, FunctionHandler, MFMessageCompos /// 拨打电话处理器 private class MakePhoneCallHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { - guard let contact = arguments["contact"] as? String else { - throw ChatApiException("缺少联系人参数") + guard let phoneNumber = arguments["phone_number"] as? String else { + throw ChatApiException("缺少电话号码参数") } - let result = await makePhoneCall(contact: contact) + let result = await makePhoneCall(phoneNumber: phoneNumber) return "{\"result\": \"\(result.message)\"}" } - private func makePhoneCall(contact: String) async -> Result { - guard let url = URL(string: "tel:\(contact)") else { + private func makePhoneCall(phoneNumber: String) async -> Result { + // 清理电话号码,移除空格和特殊字符 + let cleanedNumber = phoneNumber.replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "-", with: "") + .replacingOccurrences(of: "(", with: "") + .replacingOccurrences(of: ")", with: "") + + guard let url = URL(string: "tel:\(cleanedNumber)") else { return Result(success: false, message: "无法创建电话URL") } @@ -904,6 +930,111 @@ private class OpenRecorderHandler: FunctionHandler { // } +/// 查询通讯录处理器 +private class GetContactsHandler: FunctionHandler { + func handle(arguments: [String: Any]) async throws -> String { + let result = await getContacts() + return result + } + + private func getContacts() async -> String { + let store = CNContactStore() + + // 请求通讯录权限 + let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts) + + switch authorizationStatus { + case .authorized: + return await fetchContacts(store: store) + case .notDetermined: + // 请求权限 + do { + let granted = try await store.requestAccess(for: .contacts) + if granted { + return await fetchContacts(store: store) + } else { + return createErrorResponse(message: "用户拒绝了通讯录访问权限") + } + } catch { + return createErrorResponse(message: "请求通讯录权限时发生错误: \(error.localizedDescription)") + } + case .denied, .restricted: + return createErrorResponse(message: "无法访问通讯录,请在设置中开启通讯录权限") + @unknown default: + return createErrorResponse(message: "未知的权限状态") + } + } + + private func fetchContacts(store: CNContactStore) async -> String { + let keysToFetch = [ + CNContactGivenNameKey, + CNContactFamilyNameKey, + CNContactPhoneNumbersKey + ] as [CNKeyDescriptor] + + let request = CNContactFetchRequest(keysToFetch: keysToFetch) + var contacts: [[String: Any]] = [] + + do { + try store.enumerateContacts(with: request) { contact, _ in + let fullName = "\(contact.givenName) \(contact.familyName)".trimmingCharacters(in: .whitespaces) + + for phoneNumber in contact.phoneNumbers { + let number = phoneNumber.value.stringValue + let label = phoneNumber.label ?? "未知" + + let contactInfo: [String: Any] = [ + "name": fullName.isEmpty ? "未知联系人" : fullName, + "phone_number": number, + "label": CNLabeledValue.localizedString(forLabel: label) + ] + contacts.append(contactInfo) + } + } + + return createSuccessResponse(contacts: contacts) + } catch { + return createErrorResponse(message: "获取通讯录失败: \(error.localizedDescription)") + } + } + + private func createSuccessResponse(contacts: [[String: Any]]) -> String { + let response: [String: Any] = [ + "success": true, + "message": "成功获取通讯录", + "data": [ + "contacts": contacts, + "count": contacts.count + ] + ] + + do { + let jsonData = try JSONSerialization.data(withJSONObject: response, options: .prettyPrinted) + return String(data: jsonData, encoding: .utf8) ?? "{\"success\": false, \"message\": \"JSON序列化失败\"}" + } catch { + return "{\"success\": false, \"message\": \"JSON序列化失败: \(error.localizedDescription)\"}" + } + } + + private func createErrorResponse(message: String) -> String { + let response: [String: Any] = [ + "success": false, + "message": message, + "data": [ + "contacts": [], + "count": 0 + ] + ] + + do { + let jsonData = try JSONSerialization.data(withJSONObject: response, options: .prettyPrinted) + return String(data: jsonData, encoding: .utf8) ?? "{\"success\": false, \"message\": \"JSON序列化失败\"}" + } catch { + return "{\"success\": false, \"message\": \"JSON序列化失败: \(error.localizedDescription)\"}" + } + } +} + /// 设置用户画像字段处理器 private class SetUserProfileFieldHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String {