From c10d2e909c4febb926180c6bb7f966a225cab478 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 22 Aug 2025 19:13:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=97=A5=E5=8E=86=E6=9D=83?= =?UTF-8?q?=E9=99=90=E9=97=AE=E9=A2=98=E5=92=8C=20=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/Info.plist | 2 + .../agent_service/AgentServiceImpl.swift | 4 +- .../chat_api/ChatApiService.kt | 20 ++-- .../Sources/chat_api/ChatApiService.swift | 22 +++- .../chat_api/SystemFunctionHandler.swift | 64 +++++++++-- .../location_service/LocationService.swift | 108 ++++++------------ 6 files changed, 126 insertions(+), 94 deletions(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 4fc81d382..99c5f58ed 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -136,6 +136,8 @@ 1 + NSRemindersUsageDescription + This app needs access to reminders to set calendar reminders UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 128a4852b..fac77eafc 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -1373,10 +1373,10 @@ class ChatApiStreamCallback: StreamCallback { "result": functionCallResult, "meta":metadata, ]) - os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@", + os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@ %{metadata}@", log: agentService.logger, type: .info, - "\(broadcast)", "\(reply)") + "\(broadcast)", "\(reply)", "\(metadata)") }catch{ print("liwei--------------- AI Call onFunctionCallResult 异常: \(error)") } diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt index dec6cd009..1431295de 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt @@ -588,6 +588,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor // 通过MCP客户端处理工具调用 val functionName = firstToolCall.name val argumentsJson = firstToolCall.arguments + var isError = false + val errorText = "" val result = if (_mcpClient?.hasToolWithName(functionName) == true) { // 解析参数,如果参数为空JSON对象,使用空Map val arguments = if (argumentsJson == "{}") { @@ -595,7 +597,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } else { _mcpClient?.parseJsonArguments(argumentsJson) ?: emptyMap() } - // 调用MCP工具 val toolResult = _mcpClient?.callTool(functionName, arguments) Log.d("ChatApiService", "[Session: $sessionid] AI调用工具结果: $functionName, 参数: $arguments, 结果: $toolResult") @@ -603,9 +604,10 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor if (toolResult != null) { if (toolResult["isError"] == true) { // 处理错误情况 + isError = true val content = toolResult["content"] as? List<*> val firstContent = content?.firstOrNull() as? Map<*, *> - val errorText = firstContent?.get("text") as? String ?: "Tool execution failed" + errorText = firstContent?.get("text") as? String ?: "Tool execution failed" mapOf("context" to errorText) } else if (toolResult.containsKey("context")) { // 本地函数结果 @@ -628,11 +630,15 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor if (sessionid == currSessionId) { // 处理结果 try { - getSessionCallback(sessionid)?.onFunctionCallResult( - sessionid, - convertMapToJsonObject(functionCall), - convertMapToJsonObject(result) - ) + if(!isError){ + getSessionCallback(sessionid)?.onFunctionCallResult( + sessionid, + convertMapToJsonObject(functionCall), + convertMapToJsonObject(result) + ) + }else{ + getSessionCallback(sessionid)?.onError(sessionid, ChatApiException(errorText)) + } } catch (e: Exception) { Log.e(TAG, "onFunctionCallResult回调异常: ${e.message}", e) } diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift index 8c6a37f0f..b23b6dca5 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift @@ -481,7 +481,8 @@ getSessionCallback(sessionId)?.onFunctionCall(sessionId, jsonFunctionCall) // 通过MCP客户端处理工具调用 var result: [String: Any] = [:] - + var isError = false + var errorStr = "" if let client = self.mcpClient { // 调用MCP工具 if let toolResult = await client.callTool(name: firstToolCall.name, arguments: args) { @@ -492,6 +493,7 @@ getSessionCallback(sessionId)?.onFunctionCall(sessionId, jsonFunctionCall) if let content = toolResult["content"] as? [[String: Any]], let firstContent = content.first, let errorText = firstContent["text"] as? String { + errorStr = errorText result = ["context": errorText] } else { result = ["context": "Tool execution failed"] @@ -519,11 +521,19 @@ getSessionCallback(sessionId)?.onFunctionCall(sessionId, jsonFunctionCall) if sessionId == self.currSessionId { // 处理结果 - self.getSessionCallback(sessionId)?.onFunctionCallResult( - sessionId, - functionCall, - result - ) + if (!isError){ + self.getSessionCallback(sessionId)?.onFunctionCallResult( + sessionId, + functionCall, + result + ) + }else{ + self.getSessionCallback(sessionId)?.onError( + sessionId, + ChatApiException(errorStr) + ) + } + if (firstToolCall.name != "set_user_profile_field"){ // 将结果发送回OpenAI继续对话 await self.sendFunctionCallResultInternal( 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 a3f295014..e1b6368e3 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 @@ -84,10 +84,10 @@ public class SystemFunctionHandler: NSObject { registerGetCurrentLocationFunction(client: client) // 注册媒体控制函数 - registerMediaPlayFunction(client: client) - registerMediaPauseFunction(client: client) - registerMediaPreviousFunction(client: client) - registerMediaNextFunction(client: client) +// registerMediaPlayFunction(client: client) +// registerMediaPauseFunction(client: client) +// registerMediaPreviousFunction(client: client) +// registerMediaNextFunction(client: client) // 注册打开录音机功能 registerOpenRecorderFunction(client: client) @@ -735,8 +735,20 @@ private class SetReminderHandler: FunctionHandler { // 请求日历权限 let status = await requestCalendarAccess() - guard status == .authorized else { - return Result(success: false, message: "日历权限被拒绝") + print("[SetReminder] 权限检查结果: \(status.rawValue) - \(statusDescription(status))") + + switch status { + case .authorized: + // 权限已授权,继续执行 + break + case .denied: + return Result(success: false, message: "日历权限被拒绝,请前往 设置 > 隐私与安全 > 日历 中开启权限") + case .restricted: + return Result(success: false, message: "日历权限受限制,可能由于家长控制或企业策略限制") + case .notDetermined: + return Result(success: false, message: "日历权限未确定,请重试") + @unknown default: + return Result(success: false, message: "日历权限状态未知") } // 解析时间 @@ -765,12 +777,48 @@ private class SetReminderHandler: FunctionHandler { private func requestCalendarAccess() async -> EKAuthorizationStatus { return await withCheckedContinuation { continuation in + // 首先检查当前权限状态 + let currentStatus = EKEventStore.authorizationStatus(for: .reminder) + print("[Calendar] 当前权限状态: \(currentStatus.rawValue) - \(statusDescription(currentStatus))") + + // 如果已经授权,直接返回 + if currentStatus == .authorized { + continuation.resume(returning: currentStatus) + return + } + + // 如果被拒绝,也直接返回(避免重复请求) + if currentStatus == .denied { + print("[Calendar] 权限已被拒绝,需要用户手动在设置中开启") + continuation.resume(returning: currentStatus) + return + } + + // 请求权限 eventStore?.requestAccess(to: .reminder) { granted, error in - let status = EKEventStore.authorizationStatus(for: .reminder) - continuation.resume(returning: status) + let finalStatus = EKEventStore.authorizationStatus(for: .reminder) + print("[Calendar] 权限请求完成 - granted: \(granted), error: \(error?.localizedDescription ?? "无")") + print("[Calendar] 最终权限状态: \(finalStatus.rawValue) - \(self.statusDescription(finalStatus))") + continuation.resume(returning: finalStatus) } } } + + // 添加状态描述辅助方法 + private func statusDescription(_ status: EKAuthorizationStatus) -> String { + switch status { + case .notDetermined: + return "未确定" + case .restricted: + return "受限制" + case .denied: + return "被拒绝" + case .authorized: + return "已授权" + @unknown default: + return "未知状态" + } + } } diff --git a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift index faccc1834..a626a1e37 100644 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift +++ b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift @@ -23,13 +23,6 @@ public class LocationService: NSObject { private var authorizationStatus: CLAuthorizationStatus = .notDetermined private var isLocationServicesEnabled: Bool = false - // 高精度定位属性 - private var locationAttempts: Int = 0 - private let maxLocationAttempts: Int = 3 - private let requiredAccuracy: CLLocationAccuracy = 10.0 // 要求精度在10米以内 - private var bestLocation: CLLocation? - private var locationTimer: Timer? - // MARK: - 权限回调 private var permissionCallback: ((Result) -> Void)? @@ -43,11 +36,33 @@ public class LocationService: NSObject { self?.locationManager = CLLocationManager() self?.locationManager?.delegate = self - // 高精度定位设置 - self?.locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation // 最高精度 - self?.locationManager?.distanceFilter = 1 // 降低距离过滤器,更敏感 + // 正常定位设置 + self?.locationManager?.desiredAccuracy = kCLLocationAccuracyBest + self?.locationManager?.distanceFilter = 10 print("[LocationService] LocationManager 初始化完成,等待权限状态回调") + + // 初始化完成后,主动获取一次位置进行预热 + self?.preWarmLocation() + } + } + + /// 预热定位服务 + private func preWarmLocation() { + // 检查权限状态 + if checkLocationPermission() { + print("[LocationService] 开始预热定位") + requestLocation { [weak self] result in + switch result { + case .success(let locationData): + self?.cachedLocationData = locationData + print("[LocationService] 预热定位成功,已缓存位置数据") + case .failure(let error): + print("[LocationService] 预热定位失败: \(error.localizedDescription)") + } + } + } else { + print("[LocationService] 无定位权限,跳过预热") } } @@ -95,7 +110,7 @@ public class LocationService: NSObject { private func asyncUpdateLocation() { // 检查权限 if checkLocationPermission() { - requestHighAccuracyLocation { [weak self] result in + requestLocation { [weak self] result in switch result { case .success(let locationData): self?.cachedLocationData = locationData @@ -110,7 +125,7 @@ public class LocationService: NSObject { /// 同步获取位置 private func syncGetLocation(completion: @escaping (Result<[String: Any], Error>) -> Void) { if checkLocationPermission() { - requestHighAccuracyLocation { [weak self] result in + requestLocation { [weak self] result in switch result { case .success(let locationData): self?.cachedLocationData = locationData @@ -168,8 +183,8 @@ public class LocationService: NSObject { } } - /// 高精度定位方法 - private func requestHighAccuracyLocation(completion: @escaping (Result) -> Void) { + /// 正常定位方法 + private func requestLocation(completion: @escaping (Result) -> Void) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -190,10 +205,6 @@ public class LocationService: NSObject { return } - // 重置状态 - self.locationAttempts = 0 - self.bestLocation = nil - // 设置回调 self.currentLocationCallback = { result in switch result { @@ -217,41 +228,22 @@ public class LocationService: NSObject { } } - print("[LocationService] 开始高精度连续定位") - // 开始连续定位 - locationManager.startUpdatingLocation() - - // 设置超时定时器(15秒) - self.locationTimer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { _ in - self.stopLocationUpdates() - if let bestLocation = self.bestLocation { - self.processLocation(bestLocation) - } else { - let callback = self.currentLocationCallback - self.currentLocationCallback = nil - callback?(.failure(NSError(domain: "LocationService", code: -9, userInfo: [NSLocalizedDescriptionKey: "无法获取满足精度要求的位置"]))) - } - } + print("[LocationService] 开始正常定位") + // 使用单次定位 + locationManager.requestLocation() } } - /// 停止位置更新 - private func stopLocationUpdates() { - locationManager?.stopUpdatingLocation() - locationTimer?.invalidate() - locationTimer = nil - } - /// 处理位置数据 private func processLocation(_ location: CLLocation) { let locationInfo: [String: Any] = [ "user_lng": String(location.coordinate.longitude), "user_lat": String(location.coordinate.latitude), "timestamp": Date().timeIntervalSince1970 * 1000, - "accuracy": location.horizontalAccuracy // 添加精度信息 + "accuracy": location.horizontalAccuracy ] - print("[LocationService] 处理最终位置,精度: \(location.horizontalAccuracy)m") + print("[LocationService] 处理位置,精度: \(location.horizontalAccuracy)m") // 更新缓存 cachedLocationData = LocationData( @@ -288,8 +280,7 @@ extension LocationService: CLLocationManagerDelegate { } print("[LocationService] 位置更新: lat=\(location.coordinate.latitude), lng=\(location.coordinate.longitude)") - print("[LocationService] 水平精度: \(location.horizontalAccuracy)m, 垂直精度: \(location.verticalAccuracy)m") - print("[LocationService] 位置时间: \(location.timestamp)") + print("[LocationService] 水平精度: \(location.horizontalAccuracy)m") // 过滤无效位置 guard location.horizontalAccuracy >= 0 else { @@ -304,39 +295,14 @@ extension LocationService: CLLocationManagerDelegate { return } - locationAttempts += 1 - - // 检查是否满足精度要求 - if location.horizontalAccuracy <= requiredAccuracy { - print("[LocationService] 获得高精度位置,精度: \(location.horizontalAccuracy)m") - stopLocationUpdates() - processLocation(location) - return - } - - // 保存最佳位置 - if bestLocation == nil || location.horizontalAccuracy < bestLocation!.horizontalAccuracy { - bestLocation = location - print("[LocationService] 更新最佳位置,精度: \(location.horizontalAccuracy)m") - } - - // 达到最大尝试次数,使用最佳位置 - if locationAttempts >= maxLocationAttempts { - print("[LocationService] 达到最大尝试次数,使用最佳位置") - stopLocationUpdates() - if let bestLocation = bestLocation { - processLocation(bestLocation) - } - } + // 处理位置 + processLocation(location) } public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("[LocationService] didFailWithError 被调用") print("[LocationService] 位置获取失败: \(error.localizedDescription)") - print("[LocationService] 错误代码: \((error as NSError).code)") - print("[LocationService] 错误域: \((error as NSError).domain)") - stopLocationUpdates() let callback = currentLocationCallback currentLocationCallback = nil callback?(.failure(error))