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.
807 lines
30 KiB
807 lines
30 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",
|
|
"enum": ["short", "date", "time", "full", "iso"],
|
|
"description": "时间输出粒度,仅可使用枚举值:short(仅时分)、date(仅日期)、time(时分秒)、full(日期+星期+时间)、iso(机器可读)。禁止传入 SimpleDateFormat/strftime 等格式串。"
|
|
]
|
|
],
|
|
"required": []
|
|
]
|
|
|
|
let handler = GetCurrentTimeHandler()
|
|
_ = client.registerLocalFunction(
|
|
name: "get_current_time",
|
|
description: "获取当前日期和时间,返回值已按当前语言本地化并对TTS友好(不含冒号等易被误读的符号)。播报或回复给用户时必须原样使用 result 字段的文本,不要自行改写为 HH:mm:ss 等格式。",
|
|
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 now = Date()
|
|
let lang = Locale.current.languageCode ?? "en"
|
|
|
|
let timeStr: String
|
|
switch format?.lowercased() {
|
|
case "short":
|
|
timeStr = formatShortTime(now, lang: lang)
|
|
case "date":
|
|
timeStr = formatDate(now, lang: lang)
|
|
case "time":
|
|
timeStr = formatTime(now, lang: lang)
|
|
case "full":
|
|
timeStr = formatFull(now, lang: lang)
|
|
case "iso":
|
|
let f = DateFormatter()
|
|
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
|
|
timeStr = f.string(from: now)
|
|
default:
|
|
timeStr = formatDefault(now, lang: lang)
|
|
}
|
|
|
|
let resultTime: String
|
|
switch format?.lowercased() {
|
|
case "short", "time", "iso":
|
|
resultTime = timeStr
|
|
case "full":
|
|
resultTime = timeStr
|
|
default:
|
|
let weekFormatter = DateFormatter()
|
|
weekFormatter.dateFormat = "EEEE"
|
|
weekFormatter.locale = Locale.current
|
|
resultTime = "\(timeStr) \(weekFormatter.string(from: now))"
|
|
}
|
|
|
|
return Result(success: true, message: resultTime)
|
|
}
|
|
|
|
// TTS友好的时间格式,避免AI将":"分隔的时间误读为数字
|
|
// CJK 使用单位数 m/s(去掉前导零),其他语言使用英文单词避免冒号
|
|
private func formatShortTime(_ date: Date, lang: String) -> String {
|
|
let f = DateFormatter()
|
|
switch lang {
|
|
case "zh": f.dateFormat = "H时m分"; f.locale = Locale(identifier: "zh_CN"); return f.string(from: date)
|
|
case "ja": f.dateFormat = "H時m分"; f.locale = Locale(identifier: "ja_JP"); return f.string(from: date)
|
|
case "ko": f.dateFormat = "H시 m분"; f.locale = Locale(identifier: "ko_KR"); return f.string(from: date)
|
|
default: return formatTimeWestern(date, withSeconds: false)
|
|
}
|
|
}
|
|
|
|
private func formatDate(_ date: Date, lang: String) -> String {
|
|
let f = DateFormatter()
|
|
switch lang {
|
|
case "zh": f.dateFormat = "yyyy年M月d日"; f.locale = Locale(identifier: "zh_CN")
|
|
case "ja": f.dateFormat = "yyyy年M月d日"; f.locale = Locale(identifier: "ja_JP")
|
|
case "ko": f.dateFormat = "yyyy년 M월 d일"; f.locale = Locale(identifier: "ko_KR")
|
|
default: f.dateFormat = "MMMM d, yyyy"; f.locale = Locale.current
|
|
}
|
|
return f.string(from: date)
|
|
}
|
|
|
|
private func formatTime(_ date: Date, lang: String) -> String {
|
|
let f = DateFormatter()
|
|
switch lang {
|
|
case "zh": f.dateFormat = "H时m分s秒"; f.locale = Locale(identifier: "zh_CN"); return f.string(from: date)
|
|
case "ja": f.dateFormat = "H時m分s秒"; f.locale = Locale(identifier: "ja_JP"); return f.string(from: date)
|
|
case "ko": f.dateFormat = "H시 m분 s초"; f.locale = Locale(identifier: "ko_KR"); return f.string(from: date)
|
|
default: return formatTimeWestern(date, withSeconds: true)
|
|
}
|
|
}
|
|
|
|
private func formatFull(_ date: Date, lang: String) -> String {
|
|
let f = DateFormatter()
|
|
switch lang {
|
|
case "zh": f.dateFormat = "yyyy年M月d日 EEEE H时m分s秒"; f.locale = Locale(identifier: "zh_CN"); return f.string(from: date)
|
|
case "ja": f.dateFormat = "yyyy年M月d日 EEEE H時m分s秒"; f.locale = Locale(identifier: "ja_JP"); return f.string(from: date)
|
|
case "ko": f.dateFormat = "yyyy년 M월 d일 EEEE H시 m분 s초"; f.locale = Locale(identifier: "ko_KR"); return f.string(from: date)
|
|
default:
|
|
f.dateFormat = "MMMM d, yyyy, EEEE, "; f.locale = Locale.current
|
|
return f.string(from: date) + formatTimeWestern(date, withSeconds: true)
|
|
}
|
|
}
|
|
|
|
private func formatDefault(_ date: Date, lang: String) -> String {
|
|
let f = DateFormatter()
|
|
switch lang {
|
|
case "zh": f.dateFormat = "yyyy年M月d日 H时m分"; f.locale = Locale(identifier: "zh_CN"); return f.string(from: date)
|
|
case "ja": f.dateFormat = "yyyy年M月d日 H時m分"; f.locale = Locale(identifier: "ja_JP"); return f.string(from: date)
|
|
case "ko": f.dateFormat = "yyyy년 M월 d일 H시 m분"; f.locale = Locale(identifier: "ko_KR"); return f.string(from: date)
|
|
default:
|
|
f.dateFormat = "MMMM d, yyyy, "; f.locale = Locale.current
|
|
return f.string(from: date) + formatTimeWestern(date, withSeconds: false)
|
|
}
|
|
}
|
|
|
|
/// 非 CJK 语言的时间格式:避免冒号,使用英文单词,对 TTS 更稳定
|
|
private func formatTimeWestern(_ date: Date, withSeconds: Bool) -> String {
|
|
let cal = Calendar.current
|
|
let hour24 = cal.component(.hour, from: date)
|
|
let minute = cal.component(.minute, from: date)
|
|
let second = cal.component(.second, from: date)
|
|
let h12Raw = hour24 % 12
|
|
let h12 = h12Raw == 0 ? 12 : h12Raw
|
|
let ampm = hour24 < 12 ? "AM" : "PM"
|
|
|
|
var parts: [String] = []
|
|
parts.append("\(h12) \(h12 == 1 ? "hour" : "hours")")
|
|
if minute > 0 || withSeconds {
|
|
parts.append("\(minute) \(minute == 1 ? "minute" : "minutes")")
|
|
}
|
|
if withSeconds {
|
|
parts.append("\(second) \(second == 1 ? "second" : "seconds")")
|
|
}
|
|
parts.append(ampm)
|
|
return parts.joined(separator: " ")
|
|
}
|
|
}
|
|
|
|
/// 获取当前位置处理器
|
|
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: "无法打开录音机应用")
|
|
}
|
|
}
|