|
|
|
@ -420,16 +420,21 @@ public final class VoiceCommandExecutor { |
|
|
|
let hasAnti = normalized.contains("انتی") || normalized.contains("anti") |
|
|
|
let hasGravity = normalized.contains("گراویتی") || normalized.contains("گرویتی") || normalized.contains("gravity") |
|
|
|
|
|
|
|
if (hasAnti && hasGravity) || normalized.contains("antigravity") || normalized.contains("انتیگراویتی") || normalized.contains("انتیگرویتی") { |
|
|
|
let isIDE = normalized.contains("ای دی ای") || |
|
|
|
normalized.contains("ایدی ای") || |
|
|
|
normalized.contains("ایدیای") || |
|
|
|
normalized.contains("ide") || |
|
|
|
normalized.contains("ادیتور") || |
|
|
|
normalized.contains("کد") |
|
|
|
return isIDE ? "Antigravity IDE" : "Antigravity" |
|
|
|
guard (hasAnti && hasGravity) || normalized.contains("antigravity") || normalized.contains("انتیگراویتی") || normalized.contains("انتیگرویتی") else { |
|
|
|
return nil |
|
|
|
} |
|
|
|
return nil |
|
|
|
|
|
|
|
// Strict explicit check for IDE / Editor (Black Icon App) |
|
|
|
let isIDE = normalized.contains("ای دی ای") || |
|
|
|
normalized.contains("ایدی ای") || |
|
|
|
normalized.contains("ایدیای") || |
|
|
|
normalized.contains("آیدی ای") || |
|
|
|
normalized.contains("ide") || |
|
|
|
normalized.contains("ادیتور") |
|
|
|
|
|
|
|
// If IDE is mentioned -> "Antigravity IDE" (Black icon) |
|
|
|
// If standalone / "2" -> "Antigravity" (White icon app, Antigravity 2) |
|
|
|
return isIDE ? "Antigravity IDE" : "Antigravity" |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - Web Search & Direct Navigation |
|
|
|
@ -660,6 +665,7 @@ public final class VoiceCommandExecutor { |
|
|
|
private func parseAppAndNumber(normalized: String) -> (appName: String, number: Int, type: String)? { |
|
|
|
var targetApp: String? = nil |
|
|
|
var isTelegram = false |
|
|
|
var isAntigravity = false |
|
|
|
|
|
|
|
if normalized.contains("تلگرام") || normalized.contains("telegram") { |
|
|
|
targetApp = "Telegram" |
|
|
|
@ -671,6 +677,7 @@ public final class VoiceCommandExecutor { |
|
|
|
} else if normalized.contains("سافاری") || normalized.contains("safari") { |
|
|
|
targetApp = "Safari" |
|
|
|
} else if normalized.contains("انتی") && (normalized.contains("گراویتی") || normalized.contains("گرویتی")) { |
|
|
|
isAntigravity = true |
|
|
|
if normalized.contains("ای دی ای") || normalized.contains("ایدی ای") || normalized.contains("ide") { |
|
|
|
targetApp = "Antigravity IDE" |
|
|
|
} else { |
|
|
|
@ -683,6 +690,12 @@ public final class VoiceCommandExecutor { |
|
|
|
} |
|
|
|
|
|
|
|
guard let app = targetApp else { return nil } |
|
|
|
|
|
|
|
// For Antigravity: "آنتی گرویتی ۲" / "antigravity 2" without explicit "تب"/"tab" refers to Antigravity.app (the white icon app)! |
|
|
|
if isAntigravity && !normalized.contains("تب") && !normalized.contains("tab") { |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
guard let num = extractTrailingOrEmbeddedNumber(from: normalized) else { return nil } |
|
|
|
|
|
|
|
let type = isTelegram ? "Account" : "Tab" |
|
|
|
@ -1073,13 +1086,41 @@ public final class VoiceCommandExecutor { |
|
|
|
|
|
|
|
private func launchOrActivateApp(named appName: String) { |
|
|
|
let ws = NSWorkspace.shared |
|
|
|
|
|
|
|
// 1. Precise bundle identifier map (prevents "Antigravity" substring matching "Antigravity IDE") |
|
|
|
let bundleIDMap: [String: String] = [ |
|
|
|
"Antigravity": "com.google.antigravity", |
|
|
|
"Antigravity IDE": "com.google.antigravity-ide", |
|
|
|
"Telegram": "com.tdesktop.Telegram", |
|
|
|
"Firefox": "org.mozilla.firefox", |
|
|
|
"Google Chrome": "com.google.Chrome", |
|
|
|
"Safari": "com.apple.Safari" |
|
|
|
] |
|
|
|
|
|
|
|
if let bid = bundleIDMap[appName] { |
|
|
|
// Check currently running app by exact bundleIdentifier |
|
|
|
if let running = ws.runningApplications.first(where: { $0.bundleIdentifier == bid }) { |
|
|
|
running.activate(options: [.activateIgnoringOtherApps]) |
|
|
|
return |
|
|
|
} |
|
|
|
// If not running, launch via exact URL |
|
|
|
if let appURL = ws.urlForApplication(withBundleIdentifier: bid) { |
|
|
|
let conf = NSWorkspace.OpenConfiguration() |
|
|
|
conf.activates = true |
|
|
|
ws.openApplication(at: appURL, configuration: conf, completionHandler: nil) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 2. Strict EXACT localized name match (NOT contains!) |
|
|
|
if let running = ws.runningApplications.first(where: { |
|
|
|
$0.localizedName?.localizedCaseInsensitiveContains(appName) == true |
|
|
|
$0.localizedName?.caseInsensitiveCompare(appName) == .orderedSame |
|
|
|
}) { |
|
|
|
running.activate(options: [.activateIgnoringOtherApps]) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 3. Fallback: launch via /usr/bin/open -a with exact app name |
|
|
|
let task = Process() |
|
|
|
task.executableURL = URL(fileURLWithPath: "/usr/bin/open") |
|
|
|
task.arguments = ["-a", appName] |
|
|
|
|