Files
YuJiWatch/Phone/WatchSyncService.swift
T

82 lines
3.3 KiB
Swift

import Foundation
import Speech
import SwiftData
import WatchConnectivity
/// iPhone 端接收胶水代码:拖进 YuJi iPhone 工程即可。
/// 在 YuJiApp 启动时调用 WatchSyncService.shared.activate(modelContext:)。
/// 收到手表传来的录音文件后:拷进 AudioStorage → 系统语音识别转写 → 生成一条 VoiceNote,
/// 之后在 App 里点 AI 整理即可(和正常录音的笔记完全一样)。
final class WatchSyncService: NSObject, WCSessionDelegate, ObservableObject {
static let shared = WatchSyncService()
private var modelContext: ModelContext?
private let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "zh-CN"))
func activate(modelContext: ModelContext) {
self.modelContext = modelContext
guard WCSession.isSupported() else { return }
let session = WCSession.default
session.delegate = self
session.activate()
}
// MARK: - WCSessionDelegate
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
// 激活完成即可收文件
}
func sessionDidBecomeInactive(_ session: WCSession) {}
func sessionDidDeactivate(_ session: WCSession) {}
func session(_ session: WCSession, didReceive file: WCSessionFile) {
let fileName = file.fileURL.lastPathComponent
let destURL = AudioStorage.directory.appendingPathComponent(fileName)
do {
if FileManager.default.fileExists(atPath: destURL.path) {
try FileManager.default.removeItem(at: destURL)
}
try FileManager.default.copyItem(at: file.fileURL, to: destURL)
} catch {
print("手表录音保存失败:\(error)")
return
}
transcribeAndSave(audioURL: destURL, fileName: fileName)
}
// MARK: - 转写并建笔记
private func transcribeAndSave(audioURL: URL, fileName: String) {
guard let recognizer, recognizer.isAvailable else {
// 识别暂不可用:先建一条空转写笔记,用户可在 App 里重新整理
insertNote(title: "手表速记", transcript: "", fileName: fileName)
return
}
let request = SFSpeechURLRecognitionRequest(url: audioURL)
if recognizer.supportsOnDeviceRecognition {
request.requiresOnDeviceRecognition = true
}
recognizer.recognitionTask(with: request) { [weak self] result, error in
guard let self else { return }
guard let result, result.isFinal else {
if error != nil {
self.insertNote(title: "手表速记", transcript: "", fileName: fileName)
}
return
}
let text = result.bestTranscription.formattedString
let title = text.count > 20 ? String(text.prefix(20)) + "…" : (text.isEmpty ? "手表速记" : text)
self.insertNote(title: title, transcript: text, fileName: fileName)
}
}
@MainActor
private func insertNote(title: String, transcript: String, fileName: String) {
guard let modelContext else { return }
let note = VoiceNote(title: title, transcript: transcript, audioFileName: fileName)
modelContext.insert(note)
try? modelContext.save()
}
}