Browse Source

上传日历权限问题和 定位优化

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
cd8911a02c
  1. 2
      ios/Runner/Info.plist
  2. 4
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  3. 20
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  4. 22
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift
  5. 64
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift
  6. 108
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift

2
ios/Runner/Info.plist

@ -136,6 +136,8 @@
<array> <array>
<string>1</string> <string>1</string>
</array> </array>
<key>NSRemindersUsageDescription</key>
<string>This app needs access to reminders to set calendar reminders</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>

4
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -1373,10 +1373,10 @@ class ChatApiStreamCallback: StreamCallback {
"result": functionCallResult, "result": functionCallResult,
"meta":metadata, "meta":metadata,
]) ])
os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@", os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@ %{metadata}@",
log: agentService.logger, log: agentService.logger,
type: .info, type: .info,
"\(broadcast)", "\(reply)") "\(broadcast)", "\(reply)", "\(metadata)")
}catch{ }catch{
print("liwei--------------- AI Call onFunctionCallResult 异常: \(error)") print("liwei--------------- AI Call onFunctionCallResult 异常: \(error)")
} }

20
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客户端处理工具调用 // 通过MCP客户端处理工具调用
val functionName = firstToolCall.name val functionName = firstToolCall.name
val argumentsJson = firstToolCall.arguments val argumentsJson = firstToolCall.arguments
var isError = false
val errorText = ""
val result = if (_mcpClient?.hasToolWithName(functionName) == true) { val result = if (_mcpClient?.hasToolWithName(functionName) == true) {
// 解析参数,如果参数为空JSON对象,使用空Map // 解析参数,如果参数为空JSON对象,使用空Map
val arguments = if (argumentsJson == "{}") { val arguments = if (argumentsJson == "{}") {
@ -595,7 +597,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} else { } else {
_mcpClient?.parseJsonArguments(argumentsJson) ?: emptyMap() _mcpClient?.parseJsonArguments(argumentsJson) ?: emptyMap()
} }
// 调用MCP工具 // 调用MCP工具
val toolResult = _mcpClient?.callTool(functionName, arguments) val toolResult = _mcpClient?.callTool(functionName, arguments)
Log.d("ChatApiService", "[Session: $sessionid] AI调用工具结果: $functionName, 参数: $arguments, 结果: $toolResult") 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 != null) {
if (toolResult["isError"] == true) { if (toolResult["isError"] == true) {
// 处理错误情况 // 处理错误情况
isError = true
val content = toolResult["content"] as? List<*> val content = toolResult["content"] as? List<*>
val firstContent = content?.firstOrNull() as? Map<*, *> 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) mapOf("context" to errorText)
} else if (toolResult.containsKey("context")) { } else if (toolResult.containsKey("context")) {
// 本地函数结果 // 本地函数结果
@ -628,11 +630,15 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
if (sessionid == currSessionId) { if (sessionid == currSessionId) {
// 处理结果 // 处理结果
try { try {
getSessionCallback(sessionid)?.onFunctionCallResult( if(!isError){
sessionid, getSessionCallback(sessionid)?.onFunctionCallResult(
convertMapToJsonObject(functionCall), sessionid,
convertMapToJsonObject(result) convertMapToJsonObject(functionCall),
) convertMapToJsonObject(result)
)
}else{
getSessionCallback(sessionid)?.onError(sessionid, ChatApiException(errorText))
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "onFunctionCallResult回调异常: ${e.message}", e) Log.e(TAG, "onFunctionCallResult回调异常: ${e.message}", e)
} }

22
local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift

@ -481,7 +481,8 @@ getSessionCallback(sessionId)?.onFunctionCall(sessionId, jsonFunctionCall)
// 通过MCP客户端处理工具调用 // 通过MCP客户端处理工具调用
var result: [String: Any] = [:] var result: [String: Any] = [:]
var isError = false
var errorStr = ""
if let client = self.mcpClient { if let client = self.mcpClient {
// 调用MCP工具 // 调用MCP工具
if let toolResult = await client.callTool(name: firstToolCall.name, arguments: args) { 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]], if let content = toolResult["content"] as? [[String: Any]],
let firstContent = content.first, let firstContent = content.first,
let errorText = firstContent["text"] as? String { let errorText = firstContent["text"] as? String {
errorStr = errorText
result = ["context": errorText] result = ["context": errorText]
} else { } else {
result = ["context": "Tool execution failed"] result = ["context": "Tool execution failed"]
@ -519,11 +521,19 @@ getSessionCallback(sessionId)?.onFunctionCall(sessionId, jsonFunctionCall)
if sessionId == self.currSessionId { if sessionId == self.currSessionId {
// 处理结果 // 处理结果
self.getSessionCallback(sessionId)?.onFunctionCallResult( if (!isError){
sessionId, self.getSessionCallback(sessionId)?.onFunctionCallResult(
functionCall, sessionId,
result functionCall,
) result
)
}else{
self.getSessionCallback(sessionId)?.onError(
sessionId,
ChatApiException(errorStr)
)
}
if (firstToolCall.name != "set_user_profile_field"){ if (firstToolCall.name != "set_user_profile_field"){
// 将结果发送回OpenAI继续对话 // 将结果发送回OpenAI继续对话
await self.sendFunctionCallResultInternal( await self.sendFunctionCallResultInternal(

64
local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

@ -84,10 +84,10 @@ public class SystemFunctionHandler: NSObject {
registerGetCurrentLocationFunction(client: client) registerGetCurrentLocationFunction(client: client)
// 注册媒体控制函数 // 注册媒体控制函数
registerMediaPlayFunction(client: client) // registerMediaPlayFunction(client: client)
registerMediaPauseFunction(client: client) // registerMediaPauseFunction(client: client)
registerMediaPreviousFunction(client: client) // registerMediaPreviousFunction(client: client)
registerMediaNextFunction(client: client) // registerMediaNextFunction(client: client)
// 注册打开录音机功能 // 注册打开录音机功能
registerOpenRecorderFunction(client: client) registerOpenRecorderFunction(client: client)
@ -735,8 +735,20 @@ private class SetReminderHandler: FunctionHandler {
// 请求日历权限 // 请求日历权限
let status = await requestCalendarAccess() let status = await requestCalendarAccess()
guard status == .authorized else { print("[SetReminder] 权限检查结果: \(status.rawValue) - \(statusDescription(status))")
return Result(success: false, message: "日历权限被拒绝")
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 { private func requestCalendarAccess() async -> EKAuthorizationStatus {
return await withCheckedContinuation { continuation in 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 eventStore?.requestAccess(to: .reminder) { granted, error in
let status = EKEventStore.authorizationStatus(for: .reminder) let finalStatus = EKEventStore.authorizationStatus(for: .reminder)
continuation.resume(returning: status) 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 "未知状态"
}
}
} }

108
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 authorizationStatus: CLAuthorizationStatus = .notDetermined
private var isLocationServicesEnabled: Bool = false 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: - 权限回调 // MARK: - 权限回调
private var permissionCallback: ((Result<Bool, Error>) -> Void)? private var permissionCallback: ((Result<Bool, Error>) -> Void)?
@ -43,11 +36,33 @@ public class LocationService: NSObject {
self?.locationManager = CLLocationManager() self?.locationManager = CLLocationManager()
self?.locationManager?.delegate = self self?.locationManager?.delegate = self
// 高精度定位设置 // 正常定位设置
self?.locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation // 最高精度 self?.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
self?.locationManager?.distanceFilter = 1 // 降低距离过滤器,更敏感 self?.locationManager?.distanceFilter = 10
print("[LocationService] LocationManager 初始化完成,等待权限状态回调") 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() { private func asyncUpdateLocation() {
// 检查权限 // 检查权限
if checkLocationPermission() { if checkLocationPermission() {
requestHighAccuracyLocation { [weak self] result in requestLocation { [weak self] result in
switch result { switch result {
case .success(let locationData): case .success(let locationData):
self?.cachedLocationData = locationData self?.cachedLocationData = locationData
@ -110,7 +125,7 @@ public class LocationService: NSObject {
/// 同步获取位置 /// 同步获取位置
private func syncGetLocation(completion: @escaping (Result<[String: Any], Error>) -> Void) { private func syncGetLocation(completion: @escaping (Result<[String: Any], Error>) -> Void) {
if checkLocationPermission() { if checkLocationPermission() {
requestHighAccuracyLocation { [weak self] result in requestLocation { [weak self] result in
switch result { switch result {
case .success(let locationData): case .success(let locationData):
self?.cachedLocationData = locationData self?.cachedLocationData = locationData
@ -168,8 +183,8 @@ public class LocationService: NSObject {
} }
} }
/// 高精度定位方法 /// 正常定位方法
private func requestHighAccuracyLocation(completion: @escaping (Result<LocationData, Error>) -> Void) { private func requestLocation(completion: @escaping (Result<LocationData, Error>) -> Void) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
@ -190,10 +205,6 @@ public class LocationService: NSObject {
return return
} }
// 重置状态
self.locationAttempts = 0
self.bestLocation = nil
// 设置回调 // 设置回调
self.currentLocationCallback = { result in self.currentLocationCallback = { result in
switch result { switch result {
@ -217,41 +228,22 @@ public class LocationService: NSObject {
} }
} }
print("[LocationService] 开始高精度连续定位") print("[LocationService] 开始正常定位")
// 开始连续定位 // 使用单次定位
locationManager.startUpdatingLocation() locationManager.requestLocation()
// 设置超时定时器(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: "无法获取满足精度要求的位置"])))
}
}
} }
} }
/// 停止位置更新
private func stopLocationUpdates() {
locationManager?.stopUpdatingLocation()
locationTimer?.invalidate()
locationTimer = nil
}
/// 处理位置数据 /// 处理位置数据
private func processLocation(_ location: CLLocation) { private func processLocation(_ location: CLLocation) {
let locationInfo: [String: Any] = [ let locationInfo: [String: Any] = [
"user_lng": String(location.coordinate.longitude), "user_lng": String(location.coordinate.longitude),
"user_lat": String(location.coordinate.latitude), "user_lat": String(location.coordinate.latitude),
"timestamp": Date().timeIntervalSince1970 * 1000, "timestamp": Date().timeIntervalSince1970 * 1000,
"accuracy": location.horizontalAccuracy // 添加精度信息 "accuracy": location.horizontalAccuracy
] ]
print("[LocationService] 处理最终位置,精度: \(location.horizontalAccuracy)m") print("[LocationService] 处理位置,精度: \(location.horizontalAccuracy)m")
// 更新缓存 // 更新缓存
cachedLocationData = LocationData( cachedLocationData = LocationData(
@ -288,8 +280,7 @@ extension LocationService: CLLocationManagerDelegate {
} }
print("[LocationService] 位置更新: lat=\(location.coordinate.latitude), lng=\(location.coordinate.longitude)") print("[LocationService] 位置更新: lat=\(location.coordinate.latitude), lng=\(location.coordinate.longitude)")
print("[LocationService] 水平精度: \(location.horizontalAccuracy)m, 垂直精度: \(location.verticalAccuracy)m") print("[LocationService] 水平精度: \(location.horizontalAccuracy)m")
print("[LocationService] 位置时间: \(location.timestamp)")
// 过滤无效位置 // 过滤无效位置
guard location.horizontalAccuracy >= 0 else { guard location.horizontalAccuracy >= 0 else {
@ -304,39 +295,14 @@ extension LocationService: CLLocationManagerDelegate {
return return
} }
locationAttempts += 1 // 处理位置
processLocation(location)
// 检查是否满足精度要求
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)
}
}
} }
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("[LocationService] didFailWithError 被调用") print("[LocationService] didFailWithError 被调用")
print("[LocationService] 位置获取失败: \(error.localizedDescription)") print("[LocationService] 位置获取失败: \(error.localizedDescription)")
print("[LocationService] 错误代码: \((error as NSError).code)")
print("[LocationService] 错误域: \((error as NSError).domain)")
stopLocationUpdates()
let callback = currentLocationCallback let callback = currentLocationCallback
currentLocationCallback = nil currentLocationCallback = nil
callback?(.failure(error)) callback?(.failure(error))

Loading…
Cancel
Save