498f407585
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
102 lines
3.7 KiB
Swift
102 lines
3.7 KiB
Swift
// MacAppContextService.swift
|
|
// OSGKeyboard · Mac
|
|
//
|
|
// Unlike the iOS keyboard extension, macOS can read the frontmost app's
|
|
// bundle ID via NSWorkspace and map it to `AppContext` for polish prompts.
|
|
|
|
import AppKit
|
|
import Foundation
|
|
|
|
enum MacAppContextService {
|
|
/// Bundle IDs → coarse polish context (macOS + cross-platform).
|
|
private static let contextByBundleId: [String: AppContext] = [
|
|
// Code / dev
|
|
"com.apple.dt.Xcode": .code,
|
|
"com.microsoft.VSCode": .code,
|
|
"com.google.android.studio": .code,
|
|
"com.jetbrains.intellij": .code,
|
|
"com.jetbrains.AppCode": .code,
|
|
"com.sublimetext.4": .code,
|
|
"com.github.GitHubClient": .code,
|
|
"com.apple.Terminal": .code,
|
|
"com.googlecode.iterm2": .code,
|
|
"dev.warp.Warp-Stable": .code,
|
|
// Email
|
|
"com.apple.mail": .email,
|
|
"com.microsoft.Outlook": .email,
|
|
"com.google.Gmail": .email,
|
|
"com.readdle.smartemail": .email,
|
|
// Chat / IM
|
|
"com.tencent.xinWeChat": .chat,
|
|
"com.tencent.qq": .chat,
|
|
"com.tencent.wework": .chat,
|
|
"com.tinyspeck.slackmacgap": .chat,
|
|
"com.hnc.Discord": .chat,
|
|
"com.microsoft.teams": .chat,
|
|
"com.microsoft.teams2": .chat,
|
|
"ru.keepcoder.Telegram": .chat,
|
|
"net.whatsapp.WhatsApp": .chat,
|
|
"com.apple.MobileSMS": .chat,
|
|
"com.facebook.archon": .chat,
|
|
"com.laiwang.DingTalk": .chat,
|
|
"com.bytedance.feishu": .chat,
|
|
// Documents / notes
|
|
"com.apple.Notes": .document,
|
|
"com.apple.iWork.Pages": .document,
|
|
"notion.id": .document,
|
|
"md.obsidian": .document,
|
|
"net.shinyfrog.bear": .document,
|
|
"com.agiletortoise.Drafts-OSX": .document,
|
|
"com.microsoft.Word": .document,
|
|
"com.google.GoogleDocs": .document,
|
|
"com.evernote.Evernote": .document,
|
|
"com.microsoft.onenote.mac": .document
|
|
]
|
|
|
|
/// Chat-style apps from the shared host registry (iOS bundle IDs often
|
|
/// match Mac counterparts for cross-platform IM).
|
|
private static let chatBundleIdsFromRegistry: Set<String> = {
|
|
Set(HostAppURLRegistry.entries.map(\.bundleId))
|
|
}()
|
|
|
|
static func frontmostApplicationName() -> String? {
|
|
NSWorkspace.shared.frontmostApplication?.localizedName
|
|
}
|
|
|
|
static func frontmostBundleIdentifier() -> String? {
|
|
NSWorkspace.shared.frontmostApplication?.bundleIdentifier
|
|
}
|
|
|
|
static func detectContext() -> AppContext {
|
|
detectContext(bundleIdentifier: frontmostBundleIdentifier())
|
|
}
|
|
|
|
/// Resolve polish context from the application captured for this dictation
|
|
/// session. This avoids reading OSGKeyboard itself after a popover steals
|
|
/// focus.
|
|
static func detectContext(bundleIdentifier bundleId: String?) -> AppContext {
|
|
guard let bundleId else { return .unknown }
|
|
if let mapped = contextByBundleId[bundleId] { return mapped }
|
|
if chatBundleIdsFromRegistry.contains(bundleId) { return .chat }
|
|
if bundleId.hasPrefix("com.apple.Safari") || bundleId.contains("chrome") {
|
|
return .document
|
|
}
|
|
return .unknown
|
|
}
|
|
|
|
/// Persist detected context into the shared configuration store so
|
|
/// `PolishingService` reads the same signal as on iOS.
|
|
static func captureAndPersist(to store: AppGroupStore) {
|
|
let context = detectContext()
|
|
store.setDetectedAppContext(context)
|
|
}
|
|
|
|
static func captureAndPersist(
|
|
application: NSRunningApplication?,
|
|
to store: AppGroupStore
|
|
) {
|
|
let context = detectContext(bundleIdentifier: application?.bundleIdentifier)
|
|
store.setDetectedAppContext(context)
|
|
}
|
|
}
|