feat(keyboard): improve typing, voice flow, and polish reliability

Reduce extension memory pressure and delivery races while adding richer candidates, tactile feedback, and safer two-level creative polishing.
This commit is contained in:
Rocky
2026-08-05 21:39:31 +08:00
parent 38e5ad570d
commit 31f5937a7f
177 changed files with 8343 additions and 3904 deletions
@@ -0,0 +1,24 @@
// AppVersionDisplay.swift
// OSGKeyboardShared
//
// Reads marketing / build numbers from the host app Info.plist for Settings.
import Foundation
/// Display helpers for the app's marketing version and build number.
public enum AppVersionDisplay {
/// `CFBundleShortVersionString`, e.g. `"1.5.0"`.
public static var marketingVersion: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
}
/// `CFBundleVersion`, e.g. `"37"`.
public static var buildNumber: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? ""
}
/// Settings trailing label, e.g. `"1.5.0 (Build 37)"`.
public static var detailedLabel: String {
"\(marketingVersion) (Build \(buildNumber))"
}
}
@@ -0,0 +1,49 @@
// HostMemoryBudget.swift
// OSGKeyboard · Shared
//
// Shared RSS gate for host-side heavy work (Rime deploy, CLM, ASR warmup).
// Keeps the keyboard extension alive by avoiding host jetsam spikes.
import Foundation
public enum HostMemoryBudget {
/// Soft ceiling (MiB): defer CLM / Rime / ASR when resident memory is above this.
/// Host SwiftUI baseline alone is often ~150170 MB; 120 was always-trip and
/// left Rime/CLM permanently deferred while wrongly signaling hostHeavy.
public static let deferHeavyWorkAboveMB: Double = 260
public static let projectedPeakCeilingMB: Double = 290
public static var shouldDeferHeavyWork: Bool {
let rss = OSGDiag.memoryMB()
guard rss >= 0 else { return false }
return rss >= deferHeavyWorkAboveMB
}
public static func gate(
_ work: String,
category: String = "flow"
) -> Bool {
let rss = OSGDiag.memoryMB()
let estimatedGrowth: Double
if work.contains("clm") {
estimatedGrowth = 48
} else if work.contains("asr") {
estimatedGrowth = 32
} else if work.contains("rime") {
estimatedGrowth = 24
} else {
estimatedGrowth = 24
}
if shouldDeferHeavyWork
|| (rss >= 0 && rss + estimatedGrowth >= projectedPeakCeilingMB) {
OSGDiag.log(
"memoryGate defer work=\(work) \(OSGDiag.memoryTag()) "
+ "threshold=\(Int(deferHeavyWorkAboveMB))MB "
+ "projected=\(Int(max(0, rss + estimatedGrowth)))MB",
category: category
)
return false
}
return true
}
}
+44
View File
@@ -0,0 +1,44 @@
// OSGDiag.swift
// OSGKeyboard · Shared
//
// Always-on diagnostic breadcrumbs. Uses NSLog so lines show up in Xcode /
// Console without needing an os.Logger subsystem filter (keyboard extension
// OSLog lines are easy to miss when the host process is selected).
import Foundation
import Darwin
public enum OSGDiag {
/// Prefix every line for easy Console search: `OSGDiag`
public static func log(_ message: String, category: String = "diag") {
let line = "[OSGDiag/\(category)] \(message)"
NSLog("%@", line)
switch category {
case "keyboardExt", "boot":
OSGLog.keyboardExt.info("\(line, privacy: .public)")
case "flow", "asr":
OSGLog.flow.info("\(line, privacy: .public)")
default:
OSGLog.config.info("\(line, privacy: .public)")
}
}
/// Resident set size in MiB, or -1 if unavailable.
public static func memoryMB() -> Double {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(
MemoryLayout<mach_task_basic_info>.size / MemoryLayout<natural_t>.size
)
let kr = withUnsafeMutablePointer(to: &info) { ptr in
ptr.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { rebound in
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), rebound, &count)
}
}
guard kr == KERN_SUCCESS else { return -1 }
return Double(info.resident_size) / 1_048_576.0
}
public static func memoryTag() -> String {
String(format: "rss=%.1fMB", memoryMB())
}
}