diff --git a/src/VoiceCommandExecutor.swift b/src/VoiceCommandExecutor.swift index 1986d54..94ce0f5 100644 --- a/src/VoiceCommandExecutor.swift +++ b/src/VoiceCommandExecutor.swift @@ -196,43 +196,64 @@ public final class VoiceCommandExecutor { } public func isSlashCommand(text: String) -> Bool { - let lower = text.lowercased() + return getSlashWordPosition(text: text) != nil + } + + public func getSlashWordPosition(text: String) -> Int? { + let cleanText = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !cleanText.isEmpty else { return nil } - // Check for starting prefix - for prefix in ["اسلش", "اصلش", "slash", "slsh", "/", "\\"] { - if lower.hasPrefix(prefix) { return true } + // Starting with slash symbol, e.g. "/settings" + if cleanText.hasPrefix("/") || cleanText.hasPrefix("\\") { + return 0 } - // Check for slash trigger inside speech (e.g. after filler words like "امم، اسلش ...") - if lower.contains("اسلش") || lower.contains("اصلش") || lower.contains("slash") || lower.contains("slsh") { - return true - } + let punct = CharacterSet(charactersIn: ".,،؛:!؟?\"'«»()[]{}—-_") + let rawWords = cleanText.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty } + guard !rawWords.isEmpty else { return nil } - return false - } - - public func extractCommandBody(rawText: String) -> String { - var text = rawText.trimmingCharacters(in: .whitespacesAndNewlines) - let lower = text.lowercased() + let slashTriggers = ["اسلش", "اصلش", "slash", "slsh", "/", "\\"] - let triggers = ["اسلش", "اصلش", "slash", "slsh", "//", "/", "\\"] - var foundRange: Range? = nil + // Word 1: Direct slash command (e.g. "اسلش آنتی‌گراویتی") + let firstWordClean = rawWords[0].trimmingCharacters(in: punct).lowercased() + if slashTriggers.contains(firstWordClean) || firstWordClean.hasPrefix("/") || firstWordClean.hasPrefix("\\") { + return 0 + } - for trig in triggers { - if let r = lower.range(of: trig) { - if foundRange == nil || r.lowerBound < foundRange!.lowerBound { - foundRange = r - } + // Word 2: Slash after a single filler word (e.g. "امم اسلش فایرفاکس" or "خب، اسلش چپ") + if rawWords.count >= 2 { + let secondWordClean = rawWords[1].trimmingCharacters(in: punct).lowercased() + if slashTriggers.contains(secondWordClean) || secondWordClean.hasPrefix("/") || secondWordClean.hasPrefix("\\") { + return 1 } } - if let r = foundRange { - text = String(text[r.upperBound...]) + // STRICT RULE: If "slash" occurs at word 3 or later (middle of sentence), it is NOT a command! + return nil + } + + public func extractCommandBody(rawText: String) -> String { + guard let slashIndex = getSlashWordPosition(text: rawText) else { return "" } + + let rawWords = rawText.trimmingCharacters(in: .whitespacesAndNewlines) + .components(separatedBy: .whitespacesAndNewlines) + .filter { !$0.isEmpty } + + guard rawWords.count > slashIndex + 1 else { + // Case where command is attached to first symbol, e.g. "/settings" + if slashIndex == 0, let first = rawWords.first, first.hasPrefix("/") || first.hasPrefix("\\") { + let bodyFromFirst = String(first.dropFirst()).trimmingCharacters(in: .whitespacesAndNewlines) + let rest = rawWords.dropFirst().joined(separator: " ") + return ([bodyFromFirst, rest].filter { !$0.isEmpty }).joined(separator: " ") + } + return "" } + let bodyWords = rawWords[(slashIndex + 1)...] + var body = bodyWords.joined(separator: " ") let leadingPunct = CharacterSet(charactersIn: " :.,،؛!؟?-_—\"'«»()[]{}") - text = text.trimmingCharacters(in: leadingPunct) - return text + body = body.trimmingCharacters(in: leadingPunct) + return body } // MARK: - Main Execution Dispatcher