You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
686 lines
23 KiB
686 lines
23 KiB
import Foundation
|
|
import UIKit
|
|
import Contacts
|
|
import EventKit
|
|
import CoreLocation
|
|
import MediaPlayer
|
|
import AVFoundation
|
|
|
|
/// 操作结果
|
|
public struct Result {
|
|
let success: Bool
|
|
let message: String
|
|
|
|
public init(success: Bool, message: String) {
|
|
self.success = success
|
|
self.message = message
|
|
}
|
|
}
|
|
|
|
/// 系统功能处理器 - 负责调用系统API执行各种功能以及注册系统功能
|
|
public class SystemFunctionHandler: NSObject {
|
|
private let tag = "SystemFunctionHandler"
|
|
|
|
// 位置管理器
|
|
private var locationManager: CLLocationManager?
|
|
private var locationCompletion: ((Result) -> Void)?
|
|
|
|
// 事件存储
|
|
private var eventStore: EKEventStore?
|
|
|
|
public override init() {
|
|
super.init()
|
|
setupLocationManager()
|
|
setupEventStore()
|
|
}
|
|
|
|
/// 设置位置管理器
|
|
private func setupLocationManager() {
|
|
locationManager = CLLocationManager()
|
|
locationManager?.delegate = self
|
|
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
|
|
}
|
|
|
|
/// 设置事件存储
|
|
private func setupEventStore() {
|
|
eventStore = EKEventStore()
|
|
}
|
|
|
|
/// 在MCPClient中注册所有系统功能
|
|
/// - Parameter client: MCPClient实例
|
|
public func registerAllFunctions(client: MCPClient) {
|
|
do {
|
|
// 注册退出交互函数
|
|
registerExitInteractionFunction(client: client)
|
|
|
|
// 注册翻译模式函数
|
|
registerTranslationModeFunction(client: client)
|
|
|
|
// 注册发送短信函数
|
|
registerSendTextMessageFunction(client: client)
|
|
|
|
// 注册拨打电话函数
|
|
registerMakePhoneCallFunction(client: client)
|
|
|
|
// 注册设置提醒函数
|
|
registerSetReminderFunction(client: client)
|
|
|
|
// 注册获取当前时间函数
|
|
registerGetCurrentTimeFunction(client: client)
|
|
|
|
// 注册获取当前位置函数
|
|
registerGetCurrentLocationFunction(client: client)
|
|
|
|
// 注册媒体控制函数
|
|
registerMediaPlayFunction(client: client)
|
|
registerMediaPauseFunction(client: client)
|
|
registerMediaPreviousFunction(client: client)
|
|
registerMediaNextFunction(client: client)
|
|
|
|
// 注册打开录音机功能
|
|
registerOpenRecorderFunction(client: client)
|
|
|
|
print("\(tag) 所有系统功能注册完成")
|
|
} catch {
|
|
print("\(tag) 注册系统功能失败: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
// MARK: - 函数注册
|
|
|
|
/// 注册退出交互函数
|
|
private func registerExitInteractionFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = ExitInteractionHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "exit_interaction",
|
|
description: "结束当前交互",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册退出交互功能")
|
|
}
|
|
|
|
/// 注册翻译模式函数
|
|
private func registerTranslationModeFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = TranslationModeHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "enter_translation_mode",
|
|
description: "用户请求进入实时翻译模式时,启动实时翻译功能",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册翻译模式功能")
|
|
}
|
|
|
|
/// 注册发送短信函数
|
|
private func registerSendTextMessageFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [
|
|
"contact": [
|
|
"type": "string",
|
|
"description": "联系人姓名或电话号码"
|
|
],
|
|
"message": [
|
|
"type": "string",
|
|
"description": "短信内容"
|
|
]
|
|
],
|
|
"required": ["contact", "message"]
|
|
]
|
|
|
|
let handler = SendTextMessageHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "send_text_message",
|
|
description: "发送短信",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册发送短信功能")
|
|
}
|
|
|
|
/// 注册拨打电话函数
|
|
private func registerMakePhoneCallFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [
|
|
"contact": [
|
|
"type": "string",
|
|
"description": "联系人姓名或电话号码"
|
|
]
|
|
],
|
|
"required": ["contact"]
|
|
]
|
|
|
|
let handler = MakePhoneCallHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "make_phone_call",
|
|
description: "拨打电话",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册拨打电话功能")
|
|
}
|
|
|
|
/// 注册设置提醒函数
|
|
private func registerSetReminderFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [
|
|
"title": [
|
|
"type": "string",
|
|
"description": "提醒标题"
|
|
],
|
|
"content": [
|
|
"type": "string",
|
|
"description": "提醒内容"
|
|
],
|
|
"time": [
|
|
"type": "string",
|
|
"description": "提醒时间,格式为'yyyy-MM-dd HH:mm',如'2023-12-31 14:30'"
|
|
]
|
|
],
|
|
"required": ["title", "time"]
|
|
]
|
|
|
|
let handler = SetReminderHandler(eventStore: eventStore)
|
|
_ = client.registerLocalFunction(
|
|
name: "set_reminder",
|
|
description: "设置提醒事项",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册设置提醒功能")
|
|
}
|
|
|
|
/// 注册获取当前时间函数
|
|
private func registerGetCurrentTimeFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [
|
|
"format": [
|
|
"type": "string",
|
|
"description": "时间格式,可选,默认为标准格式"
|
|
]
|
|
],
|
|
"required": []
|
|
]
|
|
|
|
let handler = GetCurrentTimeHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "get_current_time",
|
|
description: "获取当前日期和时间",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册获取当前时间功能")
|
|
}
|
|
|
|
/// 注册获取当前位置函数
|
|
private func registerGetCurrentLocationFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = GetCurrentLocationHandler(systemHandler: self)
|
|
_ = client.registerLocalFunction(
|
|
name: "get_current_location",
|
|
description: "获取当前地理位置",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册获取当前位置功能")
|
|
}
|
|
|
|
/// 注册媒体播放功能
|
|
private func registerMediaPlayFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = MediaPlayHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "media_play",
|
|
description: "播放媒体",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册媒体播放功能")
|
|
}
|
|
|
|
/// 注册媒体暂停功能
|
|
private func registerMediaPauseFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = MediaPauseHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "media_pause",
|
|
description: "暂停媒体播放",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册媒体暂停功能")
|
|
}
|
|
|
|
/// 注册媒体上一首功能
|
|
private func registerMediaPreviousFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = MediaPreviousHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "media_previous",
|
|
description: "播放上一首",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册媒体上一首功能")
|
|
}
|
|
|
|
/// 注册媒体下一首功能
|
|
private func registerMediaNextFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = MediaNextHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "media_next",
|
|
description: "播放下一首",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册媒体下一首功能")
|
|
}
|
|
|
|
/// 注册打开录音机功能
|
|
private func registerOpenRecorderFunction(client: MCPClient) {
|
|
let parameters: [String: Any] = [
|
|
"type": "object",
|
|
"properties": [:],
|
|
"required": []
|
|
]
|
|
|
|
let handler = OpenRecorderHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "open_recorder",
|
|
description: "打开系统录音机并开始录音",
|
|
parameters: parameters,
|
|
handler: handler
|
|
)
|
|
print("\(tag) 已注册打开录音机功能")
|
|
}
|
|
|
|
// MARK: - 内部方法
|
|
|
|
/// 获取当前位置
|
|
internal func getCurrentLocation() async -> Result {
|
|
return await withCheckedContinuation { continuation in
|
|
locationCompletion = { result in
|
|
continuation.resume(returning: result)
|
|
}
|
|
|
|
guard let locationManager = locationManager else {
|
|
continuation.resume(returning: Result(success: false, message: "位置管理器未初始化"))
|
|
return
|
|
}
|
|
|
|
// 检查位置权限
|
|
let authStatus = locationManager.authorizationStatus
|
|
switch authStatus {
|
|
case .notDetermined:
|
|
locationManager.requestWhenInUseAuthorization()
|
|
return
|
|
case .denied, .restricted:
|
|
continuation.resume(returning: Result(success: false, message: "位置权限被拒绝"))
|
|
return
|
|
case .authorizedWhenInUse, .authorizedAlways:
|
|
break
|
|
@unknown default:
|
|
continuation.resume(returning: Result(success: false, message: "未知的位置权限状态"))
|
|
return
|
|
}
|
|
|
|
// 检查位置服务是否可用
|
|
guard CLLocationManager.locationServicesEnabled() else {
|
|
continuation.resume(returning: Result(success: false, message: "位置服务未启用"))
|
|
return
|
|
}
|
|
|
|
// 请求位置
|
|
locationManager.requestLocation()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - CLLocationManagerDelegate
|
|
extension SystemFunctionHandler: CLLocationManagerDelegate {
|
|
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
guard let location = locations.last else {
|
|
locationCompletion?(Result(success: false, message: "无法获取位置信息"))
|
|
return
|
|
}
|
|
|
|
let latitude = location.coordinate.latitude
|
|
let longitude = location.coordinate.longitude
|
|
let message = "当前位置:纬度 \(latitude),经度 \(longitude)"
|
|
|
|
locationCompletion?(Result(success: true, message: message))
|
|
locationCompletion = nil
|
|
}
|
|
|
|
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
locationCompletion?(Result(success: false, message: "获取位置失败:\(error.localizedDescription)"))
|
|
locationCompletion = nil
|
|
}
|
|
|
|
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
|
|
if status == .authorizedWhenInUse || status == .authorizedAlways {
|
|
manager.requestLocation()
|
|
} else if status == .denied || status == .restricted {
|
|
locationCompletion?(Result(success: false, message: "位置权限被拒绝"))
|
|
locationCompletion = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 功能处理器实现
|
|
|
|
/// 退出交互处理器
|
|
private class ExitInteractionHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
// 发送退出交互通知
|
|
NotificationCenter.default.post(name: NSNotification.Name("ExitInteraction"), object: nil)
|
|
return "{\"result\": \"已结束当前交互\"}"
|
|
}
|
|
}
|
|
|
|
/// 翻译模式处理器
|
|
private class TranslationModeHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
// 发送进入翻译模式通知
|
|
NotificationCenter.default.post(name: NSNotification.Name("EnterTranslationMode"), object: nil)
|
|
return "{\"result\": \"已进入翻译模式\"}"
|
|
}
|
|
}
|
|
|
|
/// 发送短信处理器
|
|
private class SendTextMessageHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
guard let contact = arguments["contact"] as? String,
|
|
let message = arguments["message"] as? String else {
|
|
throw OpenAIException("缺少必要参数")
|
|
}
|
|
|
|
let result = sendTextMessage(contact: contact, message: message)
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func sendTextMessage(contact: String, message: String) -> Result {
|
|
// 在iOS中,我们只能打开短信应用,无法直接发送短信
|
|
guard let url = URL(string: "sms:\(contact)&body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") else {
|
|
return Result(success: false, message: "无法创建短信URL")
|
|
}
|
|
|
|
if UIApplication.shared.canOpenURL(url) {
|
|
UIApplication.shared.open(url)
|
|
return Result(success: true, message: "已打开短信应用")
|
|
} else {
|
|
return Result(success: false, message: "无法打开短信应用")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 拨打电话处理器
|
|
private class MakePhoneCallHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
guard let contact = arguments["contact"] as? String else {
|
|
throw OpenAIException("缺少联系人参数")
|
|
}
|
|
|
|
let result = makePhoneCall(contact: contact)
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func makePhoneCall(contact: String) -> Result {
|
|
guard let url = URL(string: "tel:\(contact)") else {
|
|
return Result(success: false, message: "无法创建电话URL")
|
|
}
|
|
|
|
if UIApplication.shared.canOpenURL(url) {
|
|
UIApplication.shared.open(url)
|
|
return Result(success: true, message: "已发起电话呼叫")
|
|
} else {
|
|
return Result(success: false, message: "无法拨打电话")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 设置提醒处理器
|
|
private class SetReminderHandler: FunctionHandler {
|
|
private let eventStore: EKEventStore?
|
|
|
|
init(eventStore: EKEventStore?) {
|
|
self.eventStore = eventStore
|
|
}
|
|
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
guard let title = arguments["title"] as? String,
|
|
let timeString = arguments["time"] as? String else {
|
|
throw OpenAIException("缺少必要参数")
|
|
}
|
|
|
|
let content = arguments["content"] as? String ?? ""
|
|
let result = await setReminder(title: title, content: content, time: timeString)
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func setReminder(title: String, content: String, time: String) async -> Result {
|
|
guard let eventStore = eventStore else {
|
|
return Result(success: false, message: "事件存储未初始化")
|
|
}
|
|
|
|
// 请求日历权限
|
|
let status = await requestCalendarAccess()
|
|
guard status == .authorized else {
|
|
return Result(success: false, message: "日历权限被拒绝")
|
|
}
|
|
|
|
// 解析时间
|
|
let formatter = DateFormatter()
|
|
formatter.dateFormat = "yyyy-MM-dd HH:mm"
|
|
guard let date = formatter.date(from: time) else {
|
|
return Result(success: false, message: "时间格式错误")
|
|
}
|
|
|
|
// 创建提醒
|
|
let reminder = EKReminder(eventStore: eventStore)
|
|
reminder.title = title
|
|
reminder.notes = content
|
|
reminder.calendar = eventStore.defaultCalendarForNewReminders()
|
|
|
|
let alarm = EKAlarm(absoluteDate: date)
|
|
reminder.addAlarm(alarm)
|
|
|
|
do {
|
|
try eventStore.save(reminder, commit: true)
|
|
return Result(success: true, message: "提醒设置成功")
|
|
} catch {
|
|
return Result(success: false, message: "设置提醒失败:\(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
private func requestCalendarAccess() async -> EKAuthorizationStatus {
|
|
return await withCheckedContinuation { continuation in
|
|
eventStore?.requestAccess(to: .reminder) { granted, error in
|
|
let status = EKEventStore.authorizationStatus(for: .reminder)
|
|
continuation.resume(returning: status)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 获取当前时间处理器
|
|
private class GetCurrentTimeHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
let format = arguments["format"] as? String
|
|
let result = getCurrentTime(format: format)
|
|
return "{\"result\": \"\(result.message)\", \"time\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func getCurrentTime(format: String?) -> Result {
|
|
let formatter = DateFormatter()
|
|
|
|
if let format = format, !format.isEmpty {
|
|
formatter.dateFormat = format
|
|
} else {
|
|
formatter.dateStyle = .full
|
|
formatter.timeStyle = .full
|
|
formatter.locale = Locale(identifier: "zh_CN")
|
|
}
|
|
|
|
let currentTime = formatter.string(from: Date())
|
|
return Result(success: true, message: currentTime)
|
|
}
|
|
}
|
|
|
|
/// 获取当前位置处理器
|
|
private class GetCurrentLocationHandler: FunctionHandler {
|
|
private weak var systemHandler: SystemFunctionHandler?
|
|
|
|
init(systemHandler: SystemFunctionHandler) {
|
|
self.systemHandler = systemHandler
|
|
}
|
|
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
guard let systemHandler = systemHandler else {
|
|
throw OpenAIException("系统处理器未初始化")
|
|
}
|
|
|
|
let result = await systemHandler.getCurrentLocation()
|
|
return "{\"result\": \"\(result.message)\", \"success\": \(result.success)}"
|
|
}
|
|
}
|
|
|
|
/// 媒体播放处理器
|
|
private class MediaPlayHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
let result = controlMedia(action: "play")
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func controlMedia(action: String) -> Result {
|
|
let commandCenter = MPRemoteCommandCenter.shared()
|
|
|
|
switch action {
|
|
case "play":
|
|
// 发送播放命令通知
|
|
NotificationCenter.default.post(name: NSNotification.Name("MediaPlay"), object: nil)
|
|
return Result(success: true, message: "已开始播放媒体")
|
|
default:
|
|
return Result(success: false, message: "未知的媒体操作")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 媒体暂停处理器
|
|
private class MediaPauseHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
let result = controlMedia(action: "pause")
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func controlMedia(action: String) -> Result {
|
|
switch action {
|
|
case "pause":
|
|
// 发送暂停命令通知
|
|
NotificationCenter.default.post(name: NSNotification.Name("MediaPause"), object: nil)
|
|
return Result(success: true, message: "已暂停媒体播放")
|
|
default:
|
|
return Result(success: false, message: "未知的媒体操作")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 媒体上一首处理器
|
|
private class MediaPreviousHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
let result = controlMedia(action: "previous")
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func controlMedia(action: String) -> Result {
|
|
switch action {
|
|
case "previous":
|
|
// 发送上一首命令通知
|
|
NotificationCenter.default.post(name: NSNotification.Name("MediaPrevious"), object: nil)
|
|
return Result(success: true, message: "已切换到上一首")
|
|
default:
|
|
return Result(success: false, message: "未知的媒体操作")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 媒体下一首处理器
|
|
private class MediaNextHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
let result = controlMedia(action: "next")
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func controlMedia(action: String) -> Result {
|
|
switch action {
|
|
case "next":
|
|
// 发送下一首命令通知
|
|
NotificationCenter.default.post(name: NSNotification.Name("MediaNext"), object: nil)
|
|
return Result(success: true, message: "已切换到下一首")
|
|
default:
|
|
return Result(success: false, message: "未知的媒体操作")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 打开录音机处理器
|
|
private class OpenRecorderHandler: FunctionHandler {
|
|
func handle(arguments: [String: Any]) async throws -> String {
|
|
let result = openRecorder()
|
|
return "{\"result\": \"\(result.message)\"}"
|
|
}
|
|
|
|
private func openRecorder() -> Result {
|
|
// 尝试打开语音备忘录应用
|
|
if let url = URL(string: "voicememos://") {
|
|
if UIApplication.shared.canOpenURL(url) {
|
|
UIApplication.shared.open(url)
|
|
return Result(success: true, message: "已打开语音备忘录")
|
|
}
|
|
}
|
|
|
|
return Result(success: false, message: "无法打开录音机应用")
|
|
}
|
|
}
|