9f308fadd2
Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
153 lines
6.5 KiB
Swift
153 lines
6.5 KiB
Swift
// FlowSessionBridge+Transcription.swift
|
|
// OSGKeyboard · Shared
|
|
|
|
import Foundation
|
|
|
|
extension FlowSessionBridge {
|
|
// MARK: - Recording signals (keyboard → host)
|
|
|
|
public static func setRecordingState(
|
|
_ state: FlowSessionKeys.RecordingState,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
store.set(state.rawValue, forKey: FlowSessionKeys.keyboardRecordingState)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
}
|
|
|
|
public static func recordingState(
|
|
defaults: UserDefaults? = nil
|
|
) -> FlowSessionKeys.RecordingState {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
let raw = store.string(forKey: FlowSessionKeys.keyboardRecordingState) ?? FlowSessionKeys.RecordingState.idle.rawValue
|
|
return FlowSessionKeys.RecordingState(rawValue: raw) ?? .idle
|
|
}
|
|
|
|
public static func setTranscriptionLanguage(
|
|
_ localeId: String,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
store.set(localeId, forKey: FlowSessionKeys.transcriptionLanguage)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
}
|
|
|
|
// MARK: - Results (host → keyboard)
|
|
|
|
public static func storeTranscriptionResult(
|
|
_ text: String,
|
|
polishWarning: String? = nil,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return }
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
store.set(trimmed, forKey: FlowSessionKeys.transcriptionResult)
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionError)
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionPartial)
|
|
if let polishWarning, !polishWarning.isEmpty {
|
|
store.set(polishWarning, forKey: FlowSessionKeys.transcriptionPolishWarning)
|
|
} else {
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionPolishWarning)
|
|
}
|
|
setRecordingState(.idle, defaults: store)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
FlowSessionDarwin.postTranscriptionChanged()
|
|
}
|
|
|
|
/// Host app: publish pipelined ASR partial while recording or finalizing.
|
|
public static func storeTranscriptionPartial(
|
|
_ text: String,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
if trimmed.isEmpty {
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionPartial)
|
|
} else {
|
|
store.set(trimmed, forKey: FlowSessionKeys.transcriptionPartial)
|
|
}
|
|
FlowSessionBridgeStorage.flush(store)
|
|
FlowSessionDarwin.postTranscriptionChanged()
|
|
}
|
|
|
|
/// Keyboard: read the latest partial without clearing it.
|
|
public static func transcriptionPartial(defaults: UserDefaults? = nil) -> String? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
guard let text = store.string(forKey: FlowSessionKeys.transcriptionPartial),
|
|
!text.isEmpty else {
|
|
return nil
|
|
}
|
|
return text
|
|
}
|
|
|
|
public static func storeTranscriptionError(
|
|
_ message: String,
|
|
kind: FlowSessionKeys.TranscriptionErrorKind = .generic,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
store.set(message, forKey: FlowSessionKeys.transcriptionError)
|
|
store.set(kind.rawValue, forKey: FlowSessionKeys.transcriptionErrorKind)
|
|
setRecordingState(.idle, defaults: store)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
FlowSessionDarwin.postTranscriptionChanged()
|
|
}
|
|
|
|
/// Returns and clears a pending transcription result, if any.
|
|
public static func consumeTranscriptionResult(defaults: UserDefaults? = nil) -> String? {
|
|
consumeTranscriptionDelivery(defaults: defaults)?.text
|
|
}
|
|
|
|
/// Returns and clears a pending transcription delivery (text + optional
|
|
/// polish warning), if any.
|
|
public static func consumeTranscriptionDelivery(
|
|
defaults: UserDefaults? = nil
|
|
) -> TranscriptionDelivery? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
guard let text = store.string(forKey: FlowSessionKeys.transcriptionResult), !text.isEmpty else {
|
|
return nil
|
|
}
|
|
let warning = store.string(forKey: FlowSessionKeys.transcriptionPolishWarning)
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionResult)
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionPolishWarning)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
return TranscriptionDelivery(text: text, polishWarning: warning)
|
|
}
|
|
|
|
/// Returns and clears a pending transcription error, if any.
|
|
public static func consumeTranscriptionError(defaults: UserDefaults? = nil) -> FlowTranscriptionError? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
guard let message = store.string(forKey: FlowSessionKeys.transcriptionError), !message.isEmpty else {
|
|
return nil
|
|
}
|
|
let kindRaw = store.string(forKey: FlowSessionKeys.transcriptionErrorKind)
|
|
let kind = FlowSessionKeys.TranscriptionErrorKind(rawValue: kindRaw ?? "") ?? .generic
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionError)
|
|
store.removeObject(forKey: FlowSessionKeys.transcriptionErrorKind)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
return FlowTranscriptionError(message: message, kind: kind)
|
|
}
|
|
|
|
public static func audioLevels(defaults: UserDefaults? = nil) -> [Float] {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
if let levels = store.array(forKey: FlowSessionKeys.audioLevels) as? [Double], !levels.isEmpty {
|
|
return levels.map { Float($0) }
|
|
}
|
|
if let levels = store.array(forKey: FlowSessionKeys.audioLevels) as? [NSNumber], !levels.isEmpty {
|
|
return levels.map { $0.floatValue }
|
|
}
|
|
return []
|
|
}
|
|
|
|
/// Host app: publish waveform bars for the keyboard (main thread only).
|
|
public static func storeAudioLevels(
|
|
_ levels: [Float],
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
store.set(levels.map { Double($0) }, forKey: FlowSessionKeys.audioLevels)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
}
|
|
}
|