From fa152e16d7c59d7d59f9207d5eef351b809f26f7 Mon Sep 17 00:00:00 2001 From: Ali Alavi Date: Tue, 15 Sep 2026 20:04:20 +0000 Subject: [PATCH] fix: precise disambiguation between Antigravity (white icon) and Antigravity IDE (black icon) --- src/VoiceCommandExecutor.swift | 61 ++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/src/VoiceCommandExecutor.swift b/src/VoiceCommandExecutor.swift index bad5df7..4697fbe 100644 --- a/src/VoiceCommandExecutor.swift +++ b/src/VoiceCommandExecutor.swift @@ -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]