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.

704 lines
24 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 = await sendTextMessage(contact: contact, message: message)
return "{\"result\": \"\(result.message)\"}"
}
private func sendTextMessage(contact: String, message: String) async -> Result {
// 在iOS中,我们只能打开短信应用,无法直接发送短信
guard let url = URL(string: "sms:\(contact)&body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") else {
return Result(success: false, message: "无法创建短信URL")
}
return await withCheckedContinuation { continuation in
DispatchQueue.main.async {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url) { success in
if success {
continuation.resume(returning: Result(success: true, message: "已打开短信应用"))
} else {
continuation.resume(returning: Result(success: false, message: "打开短信应用失败"))
}
}
} else {
continuation.resume(returning: 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 = await makePhoneCall(contact: contact)
return "{\"result\": \"\(result.message)\"}"
}
private func makePhoneCall(contact: String) async -> Result {
guard let url = URL(string: "tel:\(contact)") else {
return Result(success: false, message: "无法创建电话URL")
}
return await withCheckedContinuation { continuation in
DispatchQueue.main.async {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url) { success in
if success {
continuation.resume(returning: Result(success: true, message: "已发起电话呼叫"))
} else {
continuation.resume(returning: Result(success: false, message: "发起电话呼叫失败"))
}
}
} else {
continuation.resume(returning: 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: "无法打开录音机应用")
}
}