537a68552a
Reduce perceived latency from key release to final text: - Adaptive chunking: 2.5s first chunk + 5s follow-ups so short utterances start on-device recognition while still recording. - Session-level ASR warmup and audio-format cache reuse to remove per-utterance cold-start of SpeechAnalyzer. - Mirror live pipelined partials to the keyboard transcript line via a new flow.transcriptionPartial App Group key + Darwin ping. Also commits the accumulated custom language model, Flow session, keyboard extension restructure, and Xiaomi MiMo provider work in progress on this branch.
80 lines
2.5 KiB
Swift
80 lines
2.5 KiB
Swift
// FlowSessionDarwin.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// Cross-process Darwin notification when the host app changes Flow session
|
|
// state (start, extend, end). Keyboard extension listens without polling alone.
|
|
|
|
import Foundation
|
|
|
|
public enum FlowSessionDarwin {
|
|
public static let notificationName = "com.osgkeyboard.flow.session.changed"
|
|
/// Posted when the host app writes a transcription result or error.
|
|
public static let transcriptionNotificationName = "com.osgkeyboard.flow.transcription.changed"
|
|
|
|
public static func postSessionChanged() {
|
|
CFNotificationCenterPostNotification(
|
|
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
CFNotificationName(notificationName as CFString),
|
|
nil,
|
|
nil,
|
|
true
|
|
)
|
|
}
|
|
|
|
public static func postTranscriptionChanged() {
|
|
CFNotificationCenterPostNotification(
|
|
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
CFNotificationName(transcriptionNotificationName as CFString),
|
|
nil,
|
|
nil,
|
|
true
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Observes Darwin notifications on a background thread; invokes
|
|
/// `handler` on the main actor.
|
|
public final class FlowSessionDarwinObserver {
|
|
private final class Box: @unchecked Sendable {
|
|
let handler: @MainActor () -> Void
|
|
init(handler: @escaping @MainActor () -> Void) { self.handler = handler }
|
|
}
|
|
|
|
private let box: Box
|
|
private let token: UnsafeMutableRawPointer
|
|
private let notificationName: CFString
|
|
|
|
public init(
|
|
notificationName: String = FlowSessionDarwin.notificationName,
|
|
handler: @escaping @MainActor () -> Void
|
|
) {
|
|
let box = Box(handler: handler)
|
|
self.box = box
|
|
self.token = Unmanaged.passRetained(box).toOpaque()
|
|
self.notificationName = notificationName as CFString
|
|
|
|
CFNotificationCenterAddObserver(
|
|
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
token,
|
|
{ _, observer, _, _, _ in
|
|
guard let observer else { return }
|
|
let box = Unmanaged<Box>.fromOpaque(observer).takeUnretainedValue()
|
|
Task { @MainActor in box.handler() }
|
|
},
|
|
self.notificationName,
|
|
nil,
|
|
.deliverImmediately
|
|
)
|
|
}
|
|
|
|
deinit {
|
|
CFNotificationCenterRemoveObserver(
|
|
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
token,
|
|
CFNotificationName(notificationName),
|
|
nil
|
|
)
|
|
Unmanaged<Box>.fromOpaque(token).release()
|
|
}
|
|
}
|